+ * TODO: Customize class - update intent actions, extra parameters and static
+ * helper methods.
+ */
+public class MyIntentService extends IntentService {
+
+ // read action
+ private static final String ACTION_READ_COIL = "read.coil";
+ private static final String ACTION_READ_INPUT_REGISTER = "read.input.register";
+ private static final String ACTION_READ_DISCRETE_INPUT = "read.discrete.input";
+
+ // write action
+ private static final String ACTION_WRITE_COIL = "write.coil";
+
+ private static final String ACTION_CHECK_COIL = "check.coil";
+
+ private ModbusTCPMaster master;
+
+ // TODO: Rename parameters
+ private static final String EXTRA_IP_ADDRESS = "extra.ip.address";
+ private static final String EXTRA_PORT = "extra.ip.port";
+ private static final String EXTRA_REF = "extra.ref";
+ private static final String EXTRA_COUNT = "extra.count";
+ private static final String EXTRA_INTENT_NAME = "extra.intent.name";
+
+ private static final String EXTRA_BIT_TO_SET = "extra.bit";
+ //turns out modbus on siemens logo is crap and isnt responding for at least 50ms
+ private static final Integer CAP_WAIT_TIME = 100;
+ //this is not a type, idiot.
+
+ public MyIntentService() {
+ super("MyIntentService");
+ }
+
+ private void sendBooleanListToActivity(List Note: The standard requires that the server copy whatever
+ * value the client provides. However, the transaction ID is being
+ * limited to signed 16-bit integers to prevent problems with servers
+ * that might incorrectly assume the value is a signed value.
+ */
+ int MAX_TRANSACTION_ID = Short.MAX_VALUE;
+
+ /**
+ * Defines the serial encoding "ASCII".
+ */
+ String SERIAL_ENCODING_ASCII = "ascii";
+
+ /**
+ * Defines the serial encoding "RTU".
+ */
+ String SERIAL_ENCODING_RTU = "rtu";
+
+ /**
+ * Defines the default serial encoding (ASCII).
+ */
+ String DEFAULT_SERIAL_ENCODING = SERIAL_ENCODING_ASCII;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/ModbusException.java b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusException.java
new file mode 100644
index 0000000..35ea753
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus;
+
+/**
+ * Superclass of all specialised exceptions in this package.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new ModbusException instance.
+ */
+ public ModbusException() {
+ super();
+ }
+
+ /**
+ * Constructs a new ModbusException instance with the given
+ * message.
+ *
+ *
+ * @param message the message describing this ModbusException.
+ */
+ public ModbusException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new ModbusException instance with the given
+ * message.
+ *
+ *
+ * @param message the message describing this ModbusException.
+ * @param values optional values of the exception
+ */
+ public ModbusException(String message, Object... values) {
+ super(String.format(message, values));
+ }
+
+ /**
+ * Constructs a new ModbusException instance with the given
+ * message and underlying cause.
+ *
+ *
+ * @param message the message describing this ModbusException.
+ * @param cause the cause (which is saved for later retrieval by the {@code getCause()} method).
+ * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public ModbusException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/ModbusIOException.java b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusIOException.java
new file mode 100644
index 0000000..b0f4ba6
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusIOException.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus;
+
+/**
+ * Class that implements a ModbusIOException. Instances of this
+ * exception are thrown when errors in the I/O occur.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusIOException extends ModbusException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+ private boolean eof = false;
+
+ /**
+ * Constructs a new ModbusIOException instance.
+ */
+ public ModbusIOException() {
+ }
+
+ /**
+ * Constructs a new ModbusIOException instance with the given
+ * message.
+ *
+ *
+ * @param message the message describing this ModbusIOException.
+ */
+ public ModbusIOException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new ModbusIOException instance with the given
+ * message.
+ *
+ *
+ * @param message the message describing this ModbusIOException.
+ * @param values optional values of the exception
+ */
+ public ModbusIOException(String message, Object... values) {
+ super(message, values);
+ }
+
+ /**
+ * Constructs a new ModbusIOException instance.
+ *
+ * @param b true if caused by end of stream, false otherwise.
+ */
+ public ModbusIOException(boolean b) {
+ eof = b;
+ }
+
+ /**
+ * Constructs a new ModbusIOException instance with the given
+ * message.
+ *
+ *
+ * @param message the message describing this ModbusIOException.
+ * @param b true if caused by end of stream, false otherwise.
+ */
+ public ModbusIOException(String message, boolean b) {
+ super(message);
+ eof = b;
+ }
+
+ /**
+ * Constructs a new ModbusIOException instance with the given
+ * message and underlying cause.
+ *
+ *
+ * @param message the message describing this ModbusIOException.
+ * @param cause the cause (which is saved for later retrieval by the {@code getCause()} method).
+ * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public ModbusIOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Tests if this ModbusIOException is caused by an end of the
+ * stream.
+ *
+ *
+ * @return true if stream ended, false otherwise.
+ */
+ public boolean isEOF() {
+ return eof;
+ }
+
+ /**
+ * Sets the flag that determines whether this ModbusIOException was
+ * caused by an end of the stream.
+ *
+ *
+ * @param b true if stream ended, false otherwise.
+ */
+ public void setEOF(boolean b) {
+ eof = b;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/ModbusSlaveException.java b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusSlaveException.java
new file mode 100644
index 0000000..a57e7b0
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/ModbusSlaveException.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus;
+
+/**
+ * Class that implements a ModbusSlaveException. Instances of this
+ * exception are thrown when the slave returns a Modbus exception.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusSlaveException extends ModbusException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Instance type attribute
+ */
+ private int type = -1;
+
+ /**
+ *
+ * Constructs a new ModbusSlaveException instance with the given
+ * type.
+ *
+ *
+ * Types are defined according to the protocol specification in
+ * net.wimpi.modbus.Modbus.
+ *
+ * @param type the type of exception that occurred.
+ */
+ public ModbusSlaveException(int type) {
+ super();
+
+ this.type = type;
+ }
+
+ /**
+ * Get the exception type message associated with the given exception
+ * number.
+ *
+ * @param type Numerical value of the Modbus exception.
+ *
+ * @return a String indicating the type of slave exception.
+ */
+ public static String getMessage(int type) {
+ switch (type) {
+ case 1:
+ return "Illegal Function";
+ case 2:
+ return "Illegal Data Address";
+ case 3:
+ return "Illegal Data Value";
+ case 4:
+ return "Slave Device Failure";
+ case 5:
+ return "Acknowledge";
+ case 6:
+ return "Slave Device Busy";
+ case 8:
+ return "Memory Parity Error";
+ case 10:
+ return "Gateway Path Unavailable";
+ case 11:
+ return "Gateway Target Device Failed to Respond";
+ }
+ return "Error Code = " + type;
+ }
+
+ /**
+ *
+ * Returns the type of this ModbusSlaveException.
+ * Tests if this ModbusSlaveException is of a given type.
+ *
+ *
+ * Types are defined according to the protocol specification in
+ * net.wimpi.modbus.Modbus.
+ *
+ * @param TYPE the type to test this ModbusSlaveException type
+ * against.
+ *
+ * @return true if this ModbusSlaveException is of the given type,
+ * false otherwise.
+ */
+ public boolean isType(int TYPE) {
+ return (TYPE == type);
+ }
+
+ /**
+ * Get the exception type message associated with this exception.
+ *
+ * @return a String indicating the type of slave exception.
+ */
+ public String getMessage() {
+ return getMessage(type);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/facade/AbstractModbusMaster.java b/app/src/main/java/com/ghgande/j2mod/modbus/facade/AbstractModbusMaster.java
new file mode 100644
index 0000000..fd3b797
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/facade/AbstractModbusMaster.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.facade;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusException;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.io.ModbusTransaction;
+import com.ghgande.j2mod.modbus.msg.*;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.Register;
+import com.ghgande.j2mod.modbus.util.BitVector;
+
+/**
+ * Modbus/TCP Master facade - common methods for all the facade implementations
+ * The emphasis is in making callas to Modbus devices as simple as possible
+ * for the most common Function Codes.
+ * This class makes sure that no NPE is raised and that the methods are thread-safe.
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+abstract public class AbstractModbusMaster {
+
+ private static final int DEFAULT_UNIT_ID = 1;
+
+ protected ModbusTransaction transaction;
+ private ReadCoilsRequest readCoilsRequest;
+ private ReadInputDiscretesRequest readInputDiscretesRequest;
+ private WriteCoilRequest writeCoilRequest;
+ private WriteMultipleCoilsRequest writeMultipleCoilsRequest;
+ private ReadInputRegistersRequest readInputRegistersRequest;
+ private ReadMultipleRegistersRequest readMultipleRegistersRequest;
+ private WriteSingleRegisterRequest writeSingleRegisterRequest;
+ private WriteMultipleRegistersRequest writeMultipleRegistersRequest;
+ protected int timeout = Modbus.DEFAULT_TIMEOUT;
+
+ /**
+ * Sets the transaction to use
+ *
+ * @param transaction Transaction to use
+ */
+ protected synchronized void setTransaction(ModbusTransaction transaction) {
+ this.transaction = transaction;
+ }
+
+ /**
+ * Connects this ModbusTCPMaster with the slave.
+ *
+ * @throws Exception if the connection cannot be established.
+ */
+ abstract public void connect() throws Exception;
+
+ /**
+ * Disconnects this ModbusTCPMaster from the slave.
+ */
+ abstract public void disconnect();
+
+ /**
+ * Reads a given number of coil states from the slave.
+ *
+ * Note that the number of bits in the bit vector will be
+ * forced to the number originally requested.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the coil to start reading from.
+ * @param count the number of coil states to be read.
+ *
+ * @return a BitVector instance holding the
+ * received coil states.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized BitVector readCoils(int unitId, int ref, int count) throws ModbusException {
+ checkTransaction();
+ if (readCoilsRequest == null) {
+ readCoilsRequest = new ReadCoilsRequest();
+ }
+ readCoilsRequest.setUnitID(unitId);
+ readCoilsRequest.setReference(ref);
+ readCoilsRequest.setBitCount(count);
+ transaction.setRequest(readCoilsRequest);
+ transaction.execute();
+ BitVector bv = ((ReadCoilsResponse) getAndCheckResponse()).getCoils();
+ bv.forceSize(count);
+ return bv;
+ }
+
+ /**
+ * Writes a coil state to the slave.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the coil to be written.
+ * @param state the coil state to be written.
+ *
+ * @return the state of the coil as returned from the slave.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized boolean writeCoil(int unitId, int ref, boolean state) throws ModbusException {
+ checkTransaction();
+ if (writeCoilRequest == null) {
+ writeCoilRequest = new WriteCoilRequest();
+ }
+ writeCoilRequest.setUnitID(unitId);
+ writeCoilRequest.setReference(ref);
+ writeCoilRequest.setCoil(state);
+ transaction.setRequest(writeCoilRequest);
+ transaction.execute();
+ return ((WriteCoilResponse) getAndCheckResponse()).getCoil();
+ }
+
+ /**
+ * Writes a given number of coil states to the slave.
+ *
+ * Note that the number of coils to be written is given
+ * implicitly, through {@link BitVector#size()}.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the coil to start writing to.
+ * @param coils a BitVector which holds the coil states to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeMultipleCoils(int unitId, int ref, BitVector coils) throws ModbusException {
+ checkTransaction();
+ if (writeMultipleCoilsRequest == null) {
+ writeMultipleCoilsRequest = new WriteMultipleCoilsRequest();
+ }
+ writeMultipleCoilsRequest.setUnitID(unitId);
+ writeMultipleCoilsRequest.setReference(ref);
+ writeMultipleCoilsRequest.setCoils(coils);
+ transaction.setRequest(writeMultipleCoilsRequest);
+ transaction.execute();
+ }
+
+ /**
+ * Reads a given number of input discrete states from the slave.
+ *
+ * Note that the number of bits in the bit vector will be
+ * forced to the number originally requested.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the input discrete to start reading from.
+ * @param count the number of input discrete states to be read.
+ *
+ * @return a BitVector instance holding the received input discrete
+ * states.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized BitVector readInputDiscretes(int unitId, int ref, int count) throws ModbusException {
+ checkTransaction();
+ if (readInputDiscretesRequest == null) {
+ readInputDiscretesRequest = new ReadInputDiscretesRequest();
+ }
+ readInputDiscretesRequest.setUnitID(unitId);
+ readInputDiscretesRequest.setReference(ref);
+ readInputDiscretesRequest.setBitCount(count);
+ transaction.setRequest(readInputDiscretesRequest);
+ transaction.execute();
+ BitVector bv = ((ReadInputDiscretesResponse)getAndCheckResponse()).getDiscretes();
+ bv.forceSize(count);
+ return bv;
+ }
+
+ /**
+ * Reads a given number of input registers from the slave.
+ *
+ * Note that the number of input registers returned (i.e. array length)
+ * will be according to the number received in the slave response.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the input register to start reading from.
+ * @param count the number of input registers to be read.
+ *
+ * @return a InputRegister[] with the received input registers.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized InputRegister[] readInputRegisters(int unitId, int ref, int count) throws ModbusException {
+ checkTransaction();
+ if (readInputRegistersRequest == null) {
+ readInputRegistersRequest = new ReadInputRegistersRequest();
+ }
+ readInputRegistersRequest.setUnitID(unitId);
+ readInputRegistersRequest.setReference(ref);
+ readInputRegistersRequest.setWordCount(count);
+ transaction.setRequest(readInputRegistersRequest);
+ transaction.execute();
+ return ((ReadInputRegistersResponse) getAndCheckResponse()).getRegisters();
+ }
+
+ /**
+ * Reads a given number of registers from the slave.
+ *
+ * Note that the number of registers returned (i.e. array length)
+ * will be according to the number received in the slave response.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the register to start reading from.
+ * @param count the number of registers to be read.
+ *
+ * @return a Register[] holding the received registers.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized Register[] readMultipleRegisters(int unitId, int ref, int count) throws ModbusException {
+ checkTransaction();
+ if (readMultipleRegistersRequest == null) {
+ readMultipleRegistersRequest = new ReadMultipleRegistersRequest();
+ }
+ readMultipleRegistersRequest.setUnitID(unitId);
+ readMultipleRegistersRequest.setReference(ref);
+ readMultipleRegistersRequest.setWordCount(count);
+ transaction.setRequest(readMultipleRegistersRequest);
+ transaction.execute();
+ return ((ReadMultipleRegistersResponse) getAndCheckResponse()).getRegisters();
+ }
+
+ /**
+ * Writes a single register to the slave.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the register to be written.
+ * @param register a Register holding the value of the register
+ * to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeSingleRegister(int unitId, int ref, Register register) throws ModbusException {
+ checkTransaction();
+ if (writeSingleRegisterRequest == null) {
+ writeSingleRegisterRequest = new WriteSingleRegisterRequest();
+ }
+ writeSingleRegisterRequest.setUnitID(unitId);
+ writeSingleRegisterRequest.setReference(ref);
+ writeSingleRegisterRequest.setRegister(register);
+ transaction.setRequest(writeSingleRegisterRequest);
+ transaction.execute();
+ }
+
+ /**
+ * Writes a number of registers to the slave.
+ *
+ * @param unitId the slave unit id.
+ * @param ref the offset of the register to start writing to.
+ * @param registers a Register[] holding the values of
+ * the registers to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeMultipleRegisters(int unitId, int ref, Register[] registers) throws ModbusException {
+ checkTransaction();
+ if (writeMultipleRegistersRequest == null) {
+ writeMultipleRegistersRequest = new WriteMultipleRegistersRequest();
+ }
+ writeMultipleRegistersRequest.setUnitID(unitId);
+ writeMultipleRegistersRequest.setReference(ref);
+ writeMultipleRegistersRequest.setRegisters(registers);
+ transaction.setRequest(writeMultipleRegistersRequest);
+ transaction.execute();
+ }
+
+ /**
+ * Reads a given number of coil states from the slave.
+ *
+ * Note that the number of bits in the bit vector will be
+ * forced to the number originally requested.
+ *
+ * @param ref the offset of the coil to start reading from.
+ * @param count the number of coil states to be read.
+ *
+ * @return a BitVector instance holding the
+ * received coil states.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized BitVector readCoils(int ref, int count) throws ModbusException {
+ return readCoils(DEFAULT_UNIT_ID, ref, count);
+ }
+
+ /**
+ * Writes a coil state to the slave.
+ *
+ * @param ref the offset of the coil to be written.
+ * @param state the coil state to be written.
+ *
+ * @return the state of the coil as returned from the slave.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized boolean writeCoil(int ref, boolean state) throws ModbusException {
+ return writeCoil(DEFAULT_UNIT_ID, ref, state);
+ }
+
+ /**
+ * Writes a given number of coil states to the slave.
+ *
+ * Note that the number of coils to be written is given
+ * implicitly, through {@link BitVector#size()}.
+ *
+ * @param ref the offset of the coil to start writing to.
+ * @param coils a BitVector which holds the coil states to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeMultipleCoils(int ref, BitVector coils) throws ModbusException {
+ writeMultipleCoils(DEFAULT_UNIT_ID, ref, coils);
+ }
+
+ /**
+ * Reads a given number of input discrete states from the slave.
+ *
+ * Note that the number of bits in the bit vector will be
+ * forced to the number originally requested.
+ *
+ * @param ref the offset of the input discrete to start reading from.
+ * @param count the number of input discrete states to be read.
+ *
+ * @return a BitVector instance holding the received input discrete
+ * states.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized BitVector readInputDiscretes(int ref, int count) throws ModbusException {
+ return readInputDiscretes(DEFAULT_UNIT_ID, ref, count);
+ }
+
+ /**
+ * Reads a given number of input registers from the slave.
+ *
+ * Note that the number of input registers returned (i.e. array length)
+ * will be according to the number received in the slave response.
+ *
+ * @param ref the offset of the input register to start reading from.
+ * @param count the number of input registers to be read.
+ *
+ * @return a InputRegister[] with the received input registers.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized InputRegister[] readInputRegisters(int ref, int count) throws ModbusException {
+ return readInputRegisters(DEFAULT_UNIT_ID, ref, count);
+ }
+
+ /**
+ * Reads a given number of registers from the slave.
+ *
+ * Note that the number of registers returned (i.e. array length)
+ * will be according to the number received in the slave response.
+ *
+ * @param ref the offset of the register to start reading from.
+ * @param count the number of registers to be read.
+ *
+ * @return a Register[] holding the received registers.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized Register[] readMultipleRegisters(int ref, int count) throws ModbusException {
+ return readMultipleRegisters(DEFAULT_UNIT_ID, ref, count);
+ }
+
+ /**
+ * Writes a single register to the slave.
+ *
+ * @param ref the offset of the register to be written.
+ * @param register a Register holding the value of the register
+ * to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeSingleRegister(int ref, Register register) throws ModbusException {
+ writeSingleRegister(DEFAULT_UNIT_ID, ref, register);
+ }
+
+ /**
+ * Writes a number of registers to the slave.
+ *
+ * @param ref the offset of the register to start writing to.
+ * @param registers a Register[] holding the values of
+ * the registers to be written.
+ *
+ * @throws ModbusException if an I/O error, a slave exception or
+ * a transaction error occurs.
+ */
+ public synchronized void writeMultipleRegisters(int ref, Register[] registers) throws ModbusException {
+ writeMultipleRegisters(DEFAULT_UNIT_ID, ref, registers);
+ }
+
+ /**
+ * Reads the response from the transaction
+ * If there is no response, then it throws an error
+ *
+ * @return Modbus response
+ *
+ * @throws ModbusException If response is null
+ */
+ private ModbusResponse getAndCheckResponse() throws ModbusException {
+ ModbusResponse res = transaction.getResponse();
+ if (res == null) {
+ throw new ModbusException("No response");
+ }
+ return res;
+ }
+
+ /**
+ * Checks to make sure there is a transaction to use
+ *
+ * @throws ModbusException If transaction is null
+ */
+ private void checkTransaction() throws ModbusException {
+ if (transaction == null) {
+ throw new ModbusException("No transaction created, probably not connected");
+ }
+ }
+
+ /**
+ * Returns the receive timeout in milliseconds
+ *
+ * @return Timeout in milliseconds
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the receive timeout
+ *
+ * @param timeout Timeout in milliseconds
+ */
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ /**
+ * Set the amount of retries for opening
+ * the connection for executing the transaction.
+ *
+ * @param retries the amount of retries as int.
+ */
+ synchronized public void setRetries(int retries) {
+ if (transaction != null) {
+ transaction.setRetries(retries);
+ }
+ }
+
+ /**
+ * Sets the flag that controls whether the
+ * validity of a transaction will be checked.
+ *
+ * @param b true if checking validity, false otherwise.
+ */
+ synchronized public void setCheckingValidity(boolean b) {
+ if (transaction != null) {
+ transaction.setCheckingValidity(b);
+ }
+ }
+
+ /**
+ * Returns the transport being used by the
+ *
+ * @return ModbusTransport
+ */
+ public abstract AbstractModbusTransport getTransport();
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusTCPMaster.java b/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusTCPMaster.java
new file mode 100644
index 0000000..b39ac86
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusTCPMaster.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.facade;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
+import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Modbus/TCP Master facade.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusTCPMaster extends AbstractModbusMaster {
+
+ private TCPMasterConnection connection;
+ private boolean reconnecting = false;
+ private boolean useRtuOverTcp = false;
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ */
+ public ModbusTCPMaster(String addr) {
+ this(addr, Modbus.DEFAULT_PORT, Modbus.DEFAULT_TIMEOUT, false, false);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public ModbusTCPMaster(String addr, boolean useRtuOverTcp) {
+ this(addr, Modbus.DEFAULT_PORT, Modbus.DEFAULT_TIMEOUT, false, useRtuOverTcp);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ */
+ public ModbusTCPMaster(String addr, int port) {
+ this(addr, port, Modbus.DEFAULT_TIMEOUT, false, false);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public ModbusTCPMaster(String addr, int port, boolean useRtuOverTcp) {
+ this(addr, port, Modbus.DEFAULT_TIMEOUT, false, useRtuOverTcp);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ * @param timeout Socket timeout in milliseconds
+ * @param reconnect True if the socket should reconnect if it detects a connection failure
+ */
+ public ModbusTCPMaster(String addr, int port, int timeout, boolean reconnect) {
+ this(addr, port, timeout, reconnect, false);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ * @param timeout Socket timeout in milliseconds
+ * @param reconnect True if the socket should reconnect if it detcts a connection failure
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public ModbusTCPMaster(String addr, int port, int timeout, boolean reconnect, boolean useRtuOverTcp) {
+ super();
+ this.useRtuOverTcp = useRtuOverTcp;
+ try {
+ InetAddress slaveAddress = InetAddress.getByName(addr);
+ connection = new TCPMasterConnection(slaveAddress);
+ connection.setPort(port);
+ connection.setTimeout(timeout);
+ this.timeout = timeout;
+ setReconnecting(reconnect);
+ }
+ catch (UnknownHostException e) {
+ throw new RuntimeException("Failed to contruct ModbusTCPMaster instance.", e);
+ }
+ }
+
+ /**
+ * Connects this ModbusTCPMaster with the slave.
+ *
+ * @throws Exception if the connection cannot be established.
+ */
+ public synchronized void connect() throws Exception {
+ if (connection != null && !connection.isConnected()) {
+ connection.connect(useRtuOverTcp);
+ transaction = connection.getModbusTransport().createTransaction();
+ ((ModbusTCPTransaction)transaction).setReconnecting(reconnecting);
+ setTransaction(transaction);
+ }
+ }
+
+ /**
+ * Disconnects this ModbusTCPMaster from the slave.
+ */
+ public synchronized void disconnect() {
+ if (connection != null && connection.isConnected()) {
+ connection.close();
+ transaction = null;
+ setTransaction(null);
+ }
+ }
+
+ /**
+ * Tests if a constant connection is maintained or if a new
+ * connection is established for every transaction.
+ *
+ * @return true if a new connection should be established for each
+ * transaction, false otherwise.
+ */
+ public boolean isReconnecting() {
+ return reconnecting;
+ }
+
+ /**
+ * Sets the flag that specifies whether to maintain a
+ * constant connection or reconnect for every transaction.
+ *
+ * @param b true if a new connection should be established for each
+ * transaction, false otherwise.
+ */
+ public synchronized void setReconnecting(boolean b) {
+ reconnecting = b;
+ if (transaction != null) {
+ ((ModbusTCPTransaction)transaction).setReconnecting(b);
+ }
+ }
+
+ @Override
+ public void setTimeout(int timeout) {
+ super.setTimeout(timeout);
+ if (connection != null) {
+ connection.setTimeout(timeout);
+ }
+ }
+
+ @Override
+ public AbstractModbusTransport getTransport() {
+ return connection == null ? null : connection.getModbusTransport();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusUDPMaster.java b/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusUDPMaster.java
new file mode 100644
index 0000000..793783e
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/facade/ModbusUDPMaster.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.facade;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.net.UDPMasterConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Modbus/UDP Master facade.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusUDPMaster extends AbstractModbusMaster {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusUDPMaster.class);
+
+ private UDPMasterConnection connection;
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ */
+ public ModbusUDPMaster(String addr) {
+ this(addr, Modbus.DEFAULT_PORT);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ */
+ public ModbusUDPMaster(String addr, int port) {
+ this(addr, port, Modbus.DEFAULT_TIMEOUT);
+ }
+
+ /**
+ * Constructs a new master facade instance for communication
+ * with a given slave.
+ *
+ * @param addr an internet address as resolvable IP name or IP number,
+ * specifying the slave to communicate with.
+ * @param port the port the slave is listening to.
+ * @param timeout Socket timeout in milliseconds
+ */
+ public ModbusUDPMaster(String addr, int port, int timeout) {
+ super();
+ try {
+ InetAddress slaveAddress = InetAddress.getByName(addr);
+ connection = new UDPMasterConnection(slaveAddress);
+ connection.setPort(port);
+ connection.setTimeout(timeout);
+ }
+ catch (UnknownHostException e) {
+ throw new RuntimeException("Failed to construct ModbusUDPMaster instance.", e);
+ }
+ }
+
+ /**
+ * Connects this ModbusTCPMaster with the slave.
+ *
+ * @throws Exception if the connection cannot be established.
+ */
+ public synchronized void connect() throws Exception {
+ if (connection != null && !connection.isConnected()) {
+ connection.connect();
+ transaction = connection.getModbusTransport().createTransaction();
+ setTransaction(transaction);
+ }
+ }
+
+ /**
+ * Disconnects this ModbusTCPMaster from the slave.
+ */
+ public synchronized void disconnect() {
+ if (connection != null && connection.isConnected()) {
+ connection.close();
+ transaction = null;
+ setTransaction(null);
+ }
+ }
+
+ @Override
+ public void setTimeout(int timeout) {
+ super.setTimeout(timeout);
+ if (connection != null) {
+ connection.setTimeout(timeout);
+ }
+ }
+
+ @Override
+ public AbstractModbusTransport getTransport() {
+ return connection == null ? null : connection.getModbusTransport();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractModbusTransport.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractModbusTransport.java
new file mode 100644
index 0000000..2bc9408
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractModbusTransport.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.IOException;
+
+/**
+ * Interface defining the I/O mechanisms for
+ * ModbusMessage instances.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class AbstractModbusTransport {
+
+ protected int timeout = Modbus.DEFAULT_TIMEOUT;
+
+ /**
+ * Set the socket timeout
+ *
+ * @param time Timeout in milliseconds
+ */
+ public void setTimeout(int time) {
+ timeout = time;
+ }
+
+ /**
+ * Closes the raw input and output streams of
+ * this ModbusTransport.
+ *
+ *
+ * @throws IOException if a stream
+ * cannot be closed properly.
+ */
+ public abstract void close() throws IOException;
+
+ /**
+ * Creates a Modbus transaction for the underlying transport.
+ *
+ * @return the new transaction
+ */
+ public abstract ModbusTransaction createTransaction();
+
+ /**
+ * Writes a ModbusMessage to the
+ * output stream of this ModbusTransport.
+ *
+ *
+ * @param msg a ModbusMessage.
+ *
+ * @throws ModbusIOException data cannot be
+ * written properly to the raw output stream of
+ * this ModbusTransport.
+ */
+ public abstract void writeRequest(ModbusRequest msg) throws ModbusIOException;
+
+ /**
+ * Writes a ModbusResponseMessage to the
+ * output stream of this ModbusTransport.
+ *
+ *
+ * @param msg a ModbusMessage.
+ *
+ * @throws ModbusIOException data cannot be
+ * written properly to the raw output stream of
+ * this ModbusTransport.
+ */
+ public abstract void writeResponse(ModbusResponse msg) throws ModbusIOException;
+
+ /**
+ * Reads a ModbusRequest from the
+ * input stream of this ModbusTransport.
+ *
+ *
+ * @param listener Listener the request was received by
+ *
+ * @return req the ModbusRequest read from the underlying stream.
+ *
+ * @throws ModbusIOException data cannot be
+ * read properly from the raw input stream of
+ * this ModbusTransport.
+ */
+ public abstract ModbusRequest readRequest(AbstractModbusListener listener) throws ModbusIOException;
+
+ /**
+ * Reads a ModbusResponse from the
+ * input stream of this ModbusTransport.
+ *
+ *
+ * @return res the ModbusResponse read from the underlying stream.
+ *
+ * @throws ModbusIOException data cannot be
+ * read properly from the raw input stream of
+ * this ModbusTransport.
+ */
+ public abstract ModbusResponse readResponse() throws ModbusIOException;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractSerialTransportListener.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractSerialTransportListener.java
new file mode 100644
index 0000000..cf1b44d
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/AbstractSerialTransportListener.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.msg.ModbusMessage;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.net.AbstractSerialConnection;
+
+/**
+ * Any class that wants to listen for the begining and ending of read/writes
+ * to the Serial channel need to implement this interface
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+abstract public class AbstractSerialTransportListener {
+
+ /**
+ * Will be called whenever a message is about to be written
+ *
+ * @param port Port being used
+ * @param msg Message to be written
+ */
+ public void beforeMessageWrite(AbstractSerialConnection port, ModbusMessage msg) {
+ }
+
+ /**
+ * Will be called whenever a message has been written
+ *
+ * @param port Port being used
+ * @param msg Message written
+ */
+ public void afterMessageWrite(AbstractSerialConnection port, ModbusMessage msg) {
+ }
+
+ /**
+ * Called before a request is read
+ *
+ * @param port Port to read
+ */
+ public void beforeRequestRead(AbstractSerialConnection port) {
+ }
+
+ /**
+ * Called whenever a request has been received
+ *
+ * @param port Port to read
+ * @param req Request received
+ */
+ public void afterRequestRead(AbstractSerialConnection port, ModbusRequest req) {
+ }
+
+ /**
+ * Called before a response is read
+ *
+ * @param port Port to read
+ */
+ public void beforeResponseRead(AbstractSerialConnection port) {
+ }
+
+ /**
+ * Called whenever a response has been received
+ *
+ * @param port Port to read
+ * @param res Response received
+ */
+ public void afterResponseRead(AbstractSerialConnection port, ModbusResponse res) {
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesInputStream.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesInputStream.java
new file mode 100644
index 0000000..9332178
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesInputStream.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * Class implementing a byte array input stream with
+ * a DataInput interface.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class BytesInputStream
+ extends FastByteArrayInputStream implements DataInput {
+
+ DataInputStream dataInputStream;
+
+ /**
+ * Constructs a new BytesInputStream instance,
+ * with an empty buffer of a given size.
+ *
+ * @param size the size of the input buffer.
+ */
+ public BytesInputStream(int size) {
+ super(new byte[size]);
+ dataInputStream = new DataInputStream(this);
+ }
+
+ /**
+ * Constructs a new BytesInputStream instance,
+ * that will read from the given data.
+ *
+ * @param data a byte array containing data to be read.
+ */
+ public BytesInputStream(byte[] data) {
+ super(data);
+ dataInputStream = new DataInputStream(this);
+ }
+
+ /**
+ * Resets this BytesInputStream using the given
+ * byte[] as new input buffer.
+ *
+ * @param data a byte array with data to be read.
+ */
+ public void reset(byte[] data) {
+ pos = 0;
+ mark = 0;
+ buf = data;
+ count = data.length;
+ }
+
+ /**
+ * Resets this BytesInputStream using the given
+ * byte[] as new input buffer and a given length.
+ *
+ * @param data a byte array with data to be read.
+ * @param length the length of the buffer to be considered.
+ */
+ public void reset(byte[] data, int length) {
+ pos = 0;
+ mark = 0;
+ count = length;
+ buf = data;
+ }
+
+ /**
+ * Resets this BytesInputStream assigning the input buffer
+ * a new length.
+ *
+ * @param length the length of the buffer to be considered.
+ */
+ public void reset(int length) {
+ pos = 0;
+ count = length;
+ }
+
+ /**
+ * Skips the given number of bytes or all bytes till the end
+ * of the assigned input buffer length.
+ *
+ * @param n the number of bytes to be skipped as int.
+ *
+ * @return the number of bytes skipped.
+ */
+ public int skip(int n) {
+ mark(pos);
+ pos += n;
+ return n;
+ }
+
+ /**
+ * Returns the reference to the input buffer.
+ *
+ * @return the reference to the byte[] input buffer.
+ */
+ public synchronized byte[] getBuffer() {
+ byte[] dest = new byte[buf.length];
+ System.arraycopy(buf, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ public int getBufferLength() {
+ return buf.length;
+ }
+
+ public void readFully(byte b[]) throws IOException {
+ dataInputStream.readFully(b);
+ }
+
+ public void readFully(byte b[], int off, int len) throws IOException {
+ dataInputStream.readFully(b, off, len);
+ }
+
+ public int skipBytes(int n) throws IOException {
+ return dataInputStream.skipBytes(n);
+ }
+
+ public boolean readBoolean() throws IOException {
+ return dataInputStream.readBoolean();
+ }
+
+ public byte readByte() throws IOException {
+ return dataInputStream.readByte();
+ }
+
+ public int readUnsignedByte() throws IOException {
+ return dataInputStream.readUnsignedByte();
+ }
+
+ public short readShort() throws IOException {
+ return dataInputStream.readShort();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return dataInputStream.readUnsignedShort();
+ }
+
+ public char readChar() throws IOException {
+ return dataInputStream.readChar();
+ }
+
+ public int readInt() throws IOException {
+ return dataInputStream.readInt();
+ }
+
+ public long readLong() throws IOException {
+ return dataInputStream.readLong();
+ }
+
+ public float readFloat() throws IOException {
+ return dataInputStream.readFloat();
+ }
+
+ public double readDouble() throws IOException {
+ return dataInputStream.readDouble();
+ }
+
+ public String readLine() throws IOException {
+ throw new IOException("Not supported");
+ }
+
+ public String readUTF() throws IOException {
+ return dataInputStream.readUTF();
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesOutputStream.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesOutputStream.java
new file mode 100644
index 0000000..976f1ba
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/BytesOutputStream.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Class implementing a byte array output stream with
+ * a DataInput interface.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class BytesOutputStream extends FastByteArrayOutputStream implements DataOutput {
+
+ private DataOutputStream dataOutputStream;
+
+ /**
+ * Constructs a new BytesOutputStream instance with
+ * a new output buffer of the given size.
+ *
+ * @param size the size of the output buffer as int.
+ */
+ public BytesOutputStream(int size) {
+ super(size);
+ dataOutputStream = new DataOutputStream(this);
+ }
+
+ /**
+ * Constructs a new BytesOutputStream instance with
+ * a given output buffer.
+ *
+ * @param buffer the output buffer as byte[].
+ */
+ public BytesOutputStream(byte[] buffer) {
+ buf = buffer;
+ count = 0;
+ dataOutputStream = new DataOutputStream(this);
+ }
+
+ /**
+ * Returns the reference to the output buffer.
+ *
+ * @return the reference to the byte[] output buffer.
+ */
+ public synchronized byte[] getBuffer() {
+ byte[] dest = new byte[buf.length];
+ System.arraycopy(buf, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ public void reset() {
+ count = 0;
+ }
+
+ public void writeBoolean(boolean v) throws IOException {
+ dataOutputStream.writeBoolean(v);
+ }
+
+ public void writeByte(int v) throws IOException {
+ dataOutputStream.writeByte(v);
+ }
+
+ public void writeShort(int v) throws IOException {
+ dataOutputStream.writeShort(v);
+ }
+
+ public void writeChar(int v) throws IOException {
+ dataOutputStream.writeChar(v);
+ }
+
+ public void writeInt(int v) throws IOException {
+ dataOutputStream.writeInt(v);
+ }
+
+ public void writeLong(long v) throws IOException {
+ dataOutputStream.writeLong(v);
+ }
+
+ public void writeFloat(float v) throws IOException {
+ dataOutputStream.writeFloat(v);
+ }
+
+ public void writeDouble(double v) throws IOException {
+ dataOutputStream.writeDouble(v);
+ }
+
+ public void writeBytes(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0; i < len; i++) {
+ this.write((byte)s.charAt(i));
+ }
+ }
+
+ public void writeChars(String s) throws IOException {
+ dataOutputStream.writeChars(s);
+ }
+
+ public void writeUTF(String str) throws IOException {
+ dataOutputStream.writeUTF(str);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayInputStream.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayInputStream.java
new file mode 100644
index 0000000..65b1636
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayInputStream.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class is a replacement for ByteArrayInputStream that does not
+ * synchronize every byte read.
+ *
+ * @author Mark Hayes
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class FastByteArrayInputStream extends InputStream {
+
+ private static final Logger logger = LoggerFactory.getLogger(FastByteArrayInputStream.class);
+
+ /**
+ * Number of bytes in the input buffer.
+ */
+ protected int count;
+
+ /**
+ * Actual position pointer into the input buffer.
+ */
+ int pos;
+
+ /**
+ * Marked position pointer into the input buffer.
+ */
+ int mark;
+ /**
+ * Input buffer byte[].
+ */
+ byte[] buf;
+
+ /**
+ * Creates an input stream.
+ *
+ * @param buffer the data to read.
+ */
+ FastByteArrayInputStream(byte[] buffer) {
+ buf = buffer;
+ count = buffer.length;
+ pos = 0;
+ mark = 0;
+ }
+
+ // --- begin ByteArrayInputStream compatible methods ---
+
+ public int read() throws IOException {
+ logger.debug("read()");
+ logger.debug("count={} pos={}", count, pos);
+ return (pos < count) ? (buf[pos++] & 0xff) : (-1);
+ }
+
+ public int read(byte[] toBuf) throws IOException {
+ logger.debug("read(byte[])");
+ return read(toBuf, 0, toBuf.length);
+ }
+
+ public int read(byte[] toBuf, int offset, int length) throws IOException {
+ logger.debug("read(byte[],int,int)");
+ int avail = count - pos;
+ if (avail <= 0) {
+ return -1;
+ }
+ if (length > avail) {
+ length = avail;
+ }
+ for (int i = 0; i < length; i++) {
+ toBuf[offset++] = buf[pos++];
+ }
+ return length;
+ }
+
+ public long skip(long count) {
+ int myCount = (int)count;
+ if (myCount + pos > this.count) {
+ myCount = this.count - pos;
+ }
+ pos += myCount;
+ return myCount;
+ }
+
+ public int available() {
+ return count - pos;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void mark(int readlimit) {
+ logger.debug("mark()");
+ mark = pos;
+ logger.debug("mark={} pos={}", mark, pos);
+ }
+
+ public void reset() {
+ logger.debug("reset()");
+ pos = mark;
+ logger.debug("mark={} pos={}", mark, pos);
+ }
+
+ public boolean markSupported() {
+ return true;
+ }
+
+ // --- end ByteArrayInputStream compatible methods ---
+
+ public byte[] toByteArray() {
+ byte[] toBuf = new byte[count];
+ System.arraycopy(buf, 0, toBuf, 0, count);
+ return toBuf;
+ }
+
+ /**
+ * Returns the underlying data being read.
+ *
+ * @return the underlying data.
+ */
+ public synchronized byte[] getBufferBytes() {
+ byte[] dest = new byte[count];
+ System.arraycopy(buf, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Returns the offset at which data is being read from the buffer.
+ *
+ * @return the offset at which data is being read.
+ */
+ public int getBufferOffset() {
+ return pos;
+ }
+
+ /**
+ * Returns the end of the buffer being read.
+ *
+ * @return the end of the buffer.
+ */
+ public int getBufferLength() {
+ return count;
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayOutputStream.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayOutputStream.java
new file mode 100644
index 0000000..be3a0c5
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/FastByteArrayOutputStream.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * This class is a replacement implementation for ByteArrayOutputStream
+ * that does not synchronize every
+ * byte written.
+ *
+ * @author Mark Hayes
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class FastByteArrayOutputStream extends OutputStream {
+
+ private static final Logger logger = LoggerFactory.getLogger(FastByteArrayOutputStream.class);
+
+ /**
+ * Defines the default oputput buffer size (100 bytes).
+ */
+ private static final int DEFAULT_INIT_SIZE = 100;
+ /**
+ * Defines the default increment of the output buffer size
+ * (100 bytes).
+ */
+ private static final int DEFAULT_BUMP_SIZE = 100;
+
+ /**
+ * Number of bytes in the output buffer.
+ */
+ protected int count;
+
+ /**
+ * Increment of the output buffer size on overflow.
+ */
+ private int bumpLen;
+
+ /**
+ * Output buffer byte[].
+ */
+ byte[] buf;
+
+ /**
+ * Creates an output stream with default sizes.
+ */
+ FastByteArrayOutputStream() {
+ buf = new byte[DEFAULT_INIT_SIZE];
+ bumpLen = DEFAULT_BUMP_SIZE;
+ }
+
+ /**
+ * Creates an output stream with a default bump size and a given initial
+ * size.
+ *
+ * @param initialSize the initial size of the buffer.
+ */
+ FastByteArrayOutputStream(int initialSize) {
+ buf = new byte[initialSize];
+ bumpLen = DEFAULT_BUMP_SIZE;
+ }
+
+ /**
+ * Creates an output stream with a given bump size and initial size.
+ *
+ * @param initialSize the initial size of the buffer.
+ * @param bumpSize the amount to increment the buffer.
+ */
+ public FastByteArrayOutputStream(int initialSize, int bumpSize) {
+ buf = new byte[initialSize];
+ bumpLen = bumpSize;
+ }
+
+ // --- begin ByteArrayOutputStream compatible methods ---
+
+ /**
+ * Returns the number of bytes written to this
+ * FastByteArrayOutputStream.
+ *
+ * @return the number of bytes written as int.
+ */
+ public int size() {
+ return count;
+ }
+
+ /**
+ * Resets this FastByteArrayOutputStream.
+ */
+ public void reset() {
+ count = 0;
+ }
+
+ public void write(int b) throws IOException {
+ if (count + 1 > buf.length) {
+ bump(1);
+ }
+ buf[count++] = (byte)b;
+ }
+
+ public void write(byte[] fromBuf) throws IOException {
+ int needed = count + fromBuf.length - buf.length;
+ if (needed > 0) {
+ bump(needed);
+ }
+ for (byte aFromBuf : fromBuf) {
+ buf[count++] = aFromBuf;
+ }
+ }
+
+ public void write(byte[] fromBuf, int offset, int length) throws IOException {
+
+ int needed = count + length - buf.length;
+ if (needed > 0) {
+ bump(needed);
+ }
+ int fromLen = offset + length;
+
+ for (int i = offset; i < fromLen; i++) {
+ buf[count++] = fromBuf[i];
+ }
+ }
+
+ /**
+ * Writes the content of this FastByteArrayOutputStream
+ * to the given output stream.
+ *
+ * @param out the output stream to be written to.
+ *
+ * @throws IOException if an I/O error occurs.
+ */
+ public synchronized void writeTo(OutputStream out) throws IOException {
+ out.write(buf, 0, count);
+ }
+
+ public String toString() {
+ try {
+ return new String(buf, 0, count, "US-ASCII");
+ }
+ catch (Exception e) {
+ logger.debug("Problem converting bytes to string - {}", e.getMessage());
+ }
+ return "";
+ }
+
+ /**
+ * Returns the content of this FastByteArrayOutputStream
+ * as String.
+ *
+ * @param encoding the encoding to be used for conversion.
+ *
+ * @return a newly allocated String.
+ *
+ * @throws UnsupportedEncodingException if the given encoding is not supported.
+ */
+ public String toString(String encoding) throws UnsupportedEncodingException {
+ return new String(buf, 0, count, encoding);
+ }
+
+ /**
+ * Returns the written bytes in a newly allocated byte[]
+ * of length getSize().
+ *
+ * @return a newly allocated byte[] with the content of the
+ * output buffer.
+ */
+ public byte[] toByteArray() {
+ byte[] toBuf = new byte[count];
+ System.arraycopy(buf, 0, toBuf, 0, count);
+ //for (int i = 0; i < count; i++) {
+ // toBuf[i] = buf[i];
+ //}
+ return toBuf;
+ }
+
+ // --- end ByteArrayOutputStream compatible methods ---
+
+ /**
+ * Copy the buffered data to the given array.
+ *
+ * @param toBuf the buffer to hold a copy of the data.
+ * @param offset the offset at which to start copying.
+ */
+ public void toByteArray(byte[] toBuf, int offset) {
+ int toLen = (toBuf.length > count) ? count : toBuf.length;
+ System.arraycopy(buf, offset, toBuf, offset, toLen - offset);
+ }
+
+ /**
+ * Returns the buffer owned by this object.
+ *
+ * @return the buffer.
+ */
+ public synchronized byte[] getBufferBytes() {
+ byte[] dest = new byte[count];
+ System.arraycopy(buf, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Returns the offset of the internal buffer.
+ *
+ * @return always zero currently.
+ */
+ public int getBufferOffset() {
+ return 0;
+ }
+
+ /**
+ * Returns the length used in the internal buffer, that is, the offset at
+ * which data will be written next.
+ *
+ * @return the buffer length.
+ */
+ public int getBufferLength() {
+ return count;
+ }
+
+ /**
+ * Ensure that at least the given number of bytes are available in the
+ * internal buffer.
+ *
+ * @param sizeNeeded the number of bytes desired.
+ */
+ public void makeSpace(int sizeNeeded) {
+ int needed = count + sizeNeeded - buf.length;
+ if (needed > 0) {
+ bump(needed);
+ }
+ }
+
+ /**
+ * Skip the given number of bytes in the buffer.
+ *
+ * @param sizeAdded number of bytes to skip.
+ */
+ public void addSize(int sizeAdded) {
+ count += sizeAdded;
+ }
+
+ private void bump(int needed) {
+
+ byte[] toBuf = new byte[buf.length + needed + bumpLen];
+
+ System.arraycopy(buf, 0, toBuf, 0, count);
+ buf = toBuf;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTCPTransport.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTCPTransport.java
new file mode 100644
index 0000000..fd4349d
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusRTUTCPTransport.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+
+import java.net.Socket;
+
+/**
+ * Class that implements the ModbusRTU over tCP transport flavor.
+ *
+ * @author axuan
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusRTUTCPTransport extends ModbusTCPTransport {
+
+ /**
+ * Default constructor
+ */
+ public ModbusRTUTCPTransport() {
+ // RTU over TCP is headless by default
+ setHeadless();
+ }
+
+ /**
+ * Constructs a new ModbusTransport instance, for a given
+ * Socket.
+ *
+ *
+ * @param socket the Socket used for message transport.
+ */
+ public ModbusRTUTCPTransport(Socket socket) {
+ super(socket);
+ // RTU over TCP is headless by default
+ setHeadless();
+ }
+
+ @Override
+ public void writeResponse(ModbusResponse msg) throws ModbusIOException {
+ writeMessage(msg, true);
+ }
+
+ @Override
+ public void writeRequest(ModbusRequest msg) throws ModbusIOException {
+ writeMessage(msg, true);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransaction.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransaction.java
new file mode 100644
index 0000000..f112c04
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransaction.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusException;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.ModbusSlaveException;
+import com.ghgande.j2mod.modbus.msg.ExceptionResponse;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class implementing the ModbusTransaction interface.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusTCPTransaction extends ModbusTransaction {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusTCPTransaction.class);
+
+ // instance attributes and associations
+ private TCPMasterConnection connection;
+ protected boolean reconnecting = Modbus.DEFAULT_RECONNECTING;
+
+ /**
+ * Constructs a new ModbusTCPTransaction instance.
+ */
+ public ModbusTCPTransaction() {
+ }
+
+ /**
+ * Constructs a new ModbusTCPTransaction instance with a given
+ * ModbusRequest to be send when the transaction is executed.
+ *
+ *
+ * @param request a ModbusRequest instance.
+ */
+ public ModbusTCPTransaction(ModbusRequest request) {
+ setRequest(request);
+ }
+
+ /**
+ * Constructs a new ModbusTCPTransaction instance with a given
+ * TCPMasterConnection to be used for transactions.
+ *
+ *
+ * @param con a TCPMasterConnection instance.
+ */
+ public ModbusTCPTransaction(TCPMasterConnection con) {
+ setConnection(con);
+ transport = con.getModbusTransport();
+ }
+
+ /**
+ * Sets the connection on which this ModbusTransaction should be
+ * executed.
+ *
+ * An implementation should be able to handle open and closed connections.
+ *
+ *
+ * @param con a TCPMasterConnection.
+ */
+ public synchronized void setConnection(TCPMasterConnection con) {
+ connection = con;
+ transport = con.getModbusTransport();
+ }
+
+ /**
+ * Tests if the connection will be opened and closed for each
+ * execution.
+ *
+ *
+ * @return true if reconnecting, false otherwise.
+ */
+ public boolean isReconnecting() {
+ return reconnecting;
+ }
+
+ /**
+ * Sets the flag that controls whether a connection is opened and closed
+ * for each execution or not.
+ *
+ *
+ * @param b true if reconnecting, false otherwise.
+ */
+ public void setReconnecting(boolean b) {
+ reconnecting = b;
+ }
+
+ @Override
+ public synchronized void execute() throws ModbusException {
+
+ if (request == null || connection == null) {
+ throw new ModbusException("Invalid request or connection");
+ }
+
+ // Try sending the message up to retries time. Note that the message
+ // is read immediately after being written, with no flushing of buffers.
+ int retryCounter = 0;
+ int retryLimit = (retries > 0 ? retries : Modbus.DEFAULT_RETRIES);
+ boolean keepTrying = true;
+
+ // While we haven't exhausted all the retry attempts
+ while (keepTrying) {
+
+ // Automatically connect if we aren't already connected
+ if (!connection.isConnected()) {
+ try {
+ logger.debug("Connecting to: {}:{}", connection.getAddress().toString(), connection.getPort());
+ connection.connect();
+ transport = connection.getModbusTransport();
+ }
+ catch (Exception ex) {
+ throw new ModbusIOException("Connection failed for %s:%d", connection.getAddress().toString(), connection.getPort(), ex.getMessage());
+ }
+ }
+
+ // Make sure the timeout is set
+ transport.setTimeout(connection.getTimeout());
+
+ try {
+
+ // Write the message to the endpoint
+ logger.debug("Writing request: {} (try: {}) request transaction ID = {} to {}:{}", request.getHexMessage(), retryCounter, request.getTransactionID(), connection.getAddress().toString(), connection.getPort());
+ transport.writeRequest(request);
+
+ // Read the response
+ response = transport.readResponse();
+ logger.debug("Read response: {} (try: {}) response transaction ID = {} from {}:{}", response.getHexMessage(), retryCounter, response.getTransactionID(), connection.getAddress().toString(), connection.getPort());
+ keepTrying = false;
+
+ // The slave may have returned an exception -- check for that.
+ if (response instanceof ExceptionResponse) {
+ throw new ModbusSlaveException(((ExceptionResponse)response).getExceptionCode());
+ }
+
+ // We need to keep retrying if;
+ // a) the response is empty OR
+ // b) we have been told to check the validity and the request/response transaction IDs don't match AND
+ // c) we haven't exceeded the maximum retry count
+ if (responseIsInValid()) {
+ retryCounter++;
+ if (retryCounter >= retryLimit) {
+ throw new ModbusIOException("Executing transaction failed (tried %d times)", retryLimit);
+ }
+ keepTrying = true;
+ long sleepTime = getRandomSleepTime(retryCounter);
+ if (response == null) {
+ logger.debug("Failed to get any response (try: {}) - retrying after {} milliseconds", retryCounter, sleepTime);
+ }
+ else {
+ logger.debug("Failed to get a valid response, transaction IDs do not match (try: {}) - retrying after {} milliseconds", retryCounter, sleepTime);
+ }
+ ModbusUtil.sleep(sleepTime);
+ }
+ }
+ catch (ModbusIOException ex) {
+
+ // Up the retry counter and check if we are exhausted
+ retryCounter++;
+ if (retryCounter >= retryLimit) {
+ throw new ModbusIOException("Executing transaction %s failed (tried %d times) %s", request.getHexMessage(), retryLimit, ex.getMessage());
+ }
+ else {
+ long sleepTime = getRandomSleepTime(retryCounter);
+ logger.debug("Failed transaction Request: {} (try: {}) - retrying after {} milliseconds", request.getHexMessage(), retryCounter, sleepTime);
+ ModbusUtil.sleep(sleepTime);
+ }
+
+ // If this has happened, then we should close and re-open the connection before re-trying
+ logger.debug("Failed request {} (try: {}) request transaction ID = {} - {} closing and re-opening connection {}:{}", request.getHexMessage(), retryCounter, request.getTransactionID(), ex.getMessage(), connection.getAddress().toString(), connection.getPort());
+ connection.close();
+ }
+
+ // Increment the transaction ID if we are still trying
+ if (keepTrying) {
+ incrementTransactionID();
+ }
+ }
+
+ // Close the connection if it isn't supposed to stick around.
+ if (isReconnecting()) {
+ connection.close();
+ }
+ incrementTransactionID();
+ }
+
+ /**
+ * Returns true if the response is not valid
+ * This can be if the response is null or the transaction ID of the request
+ * doesn't match the reponse
+ *
+ * @return True if invalid
+ */
+ private boolean responseIsInValid() {
+ if (response == null) {
+ return true;
+ }
+ else if (!response.isHeadless() && validityCheck) {
+ return request.getTransactionID() != response.getTransactionID();
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * incrementTransactionID -- Increment the transaction ID for the next
+ * transaction. Note that the caller must get the new transaction ID with
+ * getTransactionID(). This is only done validity checking is enabled so
+ * that dumb slaves don't cause problems. The original request will have its
+ * transaction ID incremented as well so that sending the same transaction
+ * again won't cause problems.
+ */
+ private synchronized void incrementTransactionID() {
+ if (isCheckingValidity()) {
+ if (transactionID >= Modbus.MAX_TRANSACTION_ID) {
+ transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+ }
+ else {
+ transactionID++;
+ }
+ }
+ request.setTransactionID(getTransactionID());
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransport.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransport.java
new file mode 100644
index 0000000..0b6c56e
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTCPTransport.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.msg.ModbusMessage;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+
+/**
+ * Class that implements the Modbus transport flavor.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusTCPTransport extends AbstractModbusTransport {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusTCPTransport.class);
+
+ // instance attributes
+ private DataInputStream dataInputStream; // input stream
+ private DataOutputStream dataOutputStream; // output stream
+ private final BytesInputStream byteInputStream = new BytesInputStream(Modbus.MAX_MESSAGE_LENGTH + 6);
+ private final BytesOutputStream byteOutputStream = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH + 6); // write frames
+ protected Socket socket = null;
+ protected TCPMasterConnection master = null;
+ private boolean headless = false; // Some TCP implementations are.
+
+ /**
+ * Default constructor
+ */
+ public ModbusTCPTransport() {
+ }
+
+ /**
+ * Constructs a new ModbusTransport instance, for a given
+ * Socket.
+ *
+ *
+ * @param socket the Socket used for message transport.
+ */
+ public ModbusTCPTransport(Socket socket) {
+ try {
+ setSocket(socket);
+ socket.setSoTimeout(timeout);
+ }
+ catch (IOException ex) {
+ logger.debug("ModbusTCPTransport::Socket invalid");
+
+ throw new IllegalStateException("Socket invalid", ex);
+ }
+ }
+
+ /**
+ * Sets the Socket used for message transport and prepares the
+ * streams used for the actual I/O.
+ *
+ * @param socket the Socket used for message transport.
+ *
+ * @throws IOException if an I/O related error occurs.
+ */
+ public void setSocket(Socket socket) throws IOException {
+ if (this.socket != null) {
+ this.socket.close();
+ this.socket = null;
+ }
+ this.socket = socket;
+ setTimeout(timeout);
+ prepareStreams(socket);
+ }
+
+ /**
+ * Set the transport to be headless
+ */
+ public void setHeadless() {
+ headless = true;
+ }
+
+ /**
+ * Set the transport to be headless
+ *
+ * @param headless True if headless
+ */
+ public void setHeadless(boolean headless) {
+ this.headless = headless;
+ }
+
+ /**
+ * Sets the master connection for the transport to use
+ *
+ * @param master Master
+ */
+ public void setMaster(TCPMasterConnection master) {
+ this.master = master;
+ }
+
+ @Override
+ public void setTimeout(int time) {
+ super.setTimeout(time);
+ if (socket != null) {
+ try {
+ socket.setSoTimeout(time);
+ }
+ catch (SocketException e) {
+ logger.warn("Socket exception occurred while setting timeout to " + time, e);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ dataInputStream.close();
+ dataOutputStream.close();
+ socket.close();
+ }
+
+ @Override
+ public ModbusTransaction createTransaction() {
+ if (master == null) {
+ master = new TCPMasterConnection(socket.getInetAddress());
+ master.setPort(socket.getPort());
+ master.setModbusTransport(this);
+ }
+ return new ModbusTCPTransaction(master);
+ }
+
+ @Override
+ public void writeResponse(ModbusResponse msg) throws ModbusIOException {
+ writeMessage(msg, false);
+ }
+
+ @Override
+ public void writeRequest(ModbusRequest msg) throws ModbusIOException {
+ writeMessage(msg, false);
+ }
+
+ @Override
+ public ModbusRequest readRequest(AbstractModbusListener listener) throws ModbusIOException {
+ ModbusRequest req;
+ try {
+ byteInputStream.reset();
+
+ synchronized (byteInputStream) {
+ byte[] buffer = byteInputStream.getBuffer();
+
+ if (!headless) {
+ dataInputStream.readFully(buffer, 0, 6);
+
+ // The transaction ID must be treated as an unsigned short in
+ // order for validation to work correctly.
+
+ int transaction = ModbusUtil.registerToShort(buffer, 0) & 0x0000FFFF;
+ int protocol = ModbusUtil.registerToShort(buffer, 2);
+ int count = ModbusUtil.registerToShort(buffer, 4);
+
+ dataInputStream.readFully(buffer, 6, count);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Read: {}", ModbusUtil.toHex(buffer, 0, count + 6));
+ }
+
+ byteInputStream.reset(buffer, (6 + count));
+ byteInputStream.skip(6);
+
+ int unit = byteInputStream.readByte();
+ int functionCode = byteInputStream.readUnsignedByte();
+
+ byteInputStream.reset();
+ req = ModbusRequest.createModbusRequest(functionCode);
+ req.setUnitID(unit);
+ req.setHeadless(false);
+
+ req.setTransactionID(transaction);
+ req.setProtocolID(protocol);
+ req.setDataLength(count);
+
+ req.readFrom(byteInputStream);
+ }
+ else {
+
+ // This is a headless request.
+
+ int unit = dataInputStream.readByte();
+ int function = dataInputStream.readByte();
+
+ req = ModbusRequest.createModbusRequest(function);
+ req.setUnitID(unit);
+ req.setHeadless(true);
+ req.readData(dataInputStream);
+
+ // Discard the CRC. This is a TCP/IP connection, which has
+ // proper error correction and recovery.
+
+ dataInputStream.readShort();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Read: {}", req.getHexMessage());
+ }
+ }
+ }
+ return req;
+ }
+ catch (EOFException eoex) {
+ throw new ModbusIOException("End of File", true);
+ }
+ catch (SocketTimeoutException x) {
+ throw new ModbusIOException("Timeout reading request", x);
+ }
+ catch (SocketException sockex) {
+ throw new ModbusIOException("Socket Exception", sockex);
+ }
+ catch (IOException ex) {
+ throw new ModbusIOException("I/O exception - failed to read", ex);
+ }
+ }
+
+ @Override
+ public ModbusResponse readResponse() throws ModbusIOException {
+ try {
+ ModbusResponse response;
+
+ synchronized (byteInputStream) {
+ // use same buffer
+ byte[] buffer = byteInputStream.getBuffer();
+ logger.debug("Reading response...");
+ if (!headless) {
+ // All Modbus TCP transactions start with 6 bytes. Get them.
+ dataInputStream.readFully(buffer, 0, 6);
+
+ /*
+ * The transaction ID is the first word (offset 0) in the
+ * data that was just read. It will be echoed back to the
+ * requester.
+ *
+ * The protocol ID is the second word (offset 2) in the
+ * data. It should always be 0, but I don't check.
+ *
+ * The length of the payload is the third word (offset 4) in
+ * the data that was just read. That's what I need in order
+ * to read the rest of the response.
+ */
+ int transaction = ModbusUtil.registerToShort(buffer, 0) & 0x0000FFFF;
+ int protocol = ModbusUtil.registerToShort(buffer, 2);
+ int count = ModbusUtil.registerToShort(buffer, 4);
+
+ dataInputStream.readFully(buffer, 6, count);
+ byteInputStream.reset(buffer, (6 + count));
+ byteInputStream.reset();
+ byteInputStream.skip(7);
+ int function = byteInputStream.readUnsignedByte();
+ response = ModbusResponse.createModbusResponse(function);
+
+ // Rewind the input buffer, then read the data into the
+ // response.
+ byteInputStream.reset();
+ response.readFrom(byteInputStream);
+
+ response.setTransactionID(transaction);
+ response.setProtocolID(protocol);
+ }
+ else {
+ // This is a headless response. It has the same format as a
+ // RTU over Serial response.
+ int unit = dataInputStream.readByte();
+ int function = dataInputStream.readByte();
+
+ response = ModbusResponse.createModbusResponse(function);
+ response.setUnitID(unit);
+ response.setHeadless();
+ response.readData(dataInputStream);
+
+ // Now discard the CRC. Which hopefully wasn't needed
+ // because this is a TCP transport.
+ dataInputStream.readShort();
+ }
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Successfully read: {}", response.getHexMessage());
+ }
+ return response;
+ }
+ catch (EOFException ex1) {
+ throw new ModbusIOException("Premature end of stream (Message truncated) - %s", ex1.getMessage());
+ }
+ catch (SocketTimeoutException ex2) {
+ throw new ModbusIOException("Socket timeout reading response - %s", ex2.getMessage());
+ }
+ catch (Exception ex3) {
+ throw new ModbusIOException("General exception - failed to read - %s", ex3.getMessage());
+ }
+ }
+
+ /**
+ * Prepares the input and output streams of this ModbusTCPTransport
+ * instance based on the given socket.
+ *
+ * @param socket the socket used for communications.
+ *
+ * @throws IOException if an I/O related error occurs.
+ */
+ private void prepareStreams(Socket socket) throws IOException {
+
+ // Close any open streams if I'm being called because a new socket was
+ // set to handle this transport.
+ try {
+ if (dataInputStream != null) {
+ dataInputStream.close();
+ }
+ if (dataOutputStream != null) {
+ dataOutputStream.close();
+ }
+ }
+ catch (IOException x) {
+ // Do nothing.
+ }
+
+ dataInputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
+ dataOutputStream = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
+ }
+
+ /**
+ * Writes a ModbusMessage to the
+ * output stream of this ModbusTransport.
+ *
+ *
+ * @param msg a ModbusMessage.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ *
+ * @throws ModbusIOException data cannot be
+ * written properly to the raw output stream of
+ * this ModbusTransport.
+ */
+ void writeMessage(ModbusMessage msg, boolean useRtuOverTcp) throws ModbusIOException {
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Sending: {}", msg.getHexMessage());
+ }
+ byte message[] = msg.getMessage();
+
+ byteOutputStream.reset();
+ if (!headless) {
+ byteOutputStream.writeShort(msg.getTransactionID());
+ byteOutputStream.writeShort(msg.getProtocolID());
+ byteOutputStream.writeShort((message != null ? message.length : 0) + 2);
+ }
+ byteOutputStream.writeByte(msg.getUnitID());
+ byteOutputStream.writeByte(msg.getFunctionCode());
+ if (message != null && message.length > 0) {
+ byteOutputStream.write(message);
+ }
+
+ // Add CRC for RTU over TCP
+ if (useRtuOverTcp) {
+ int len = byteOutputStream.size();
+ int[] crc = ModbusUtil.calculateCRC(byteOutputStream.getBuffer(), 0, len);
+ byteOutputStream.writeByte(crc[0]);
+ byteOutputStream.writeByte(crc[1]);
+ }
+
+ dataOutputStream.write(byteOutputStream.toByteArray());
+ dataOutputStream.flush();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Successfully sent: {}", ModbusUtil.toHex(byteOutputStream.toByteArray()));
+ }
+ // write more sophisticated exception handling
+ }
+ catch (SocketException ex1) {
+ if (master != null && !master.isConnected()) {
+ try {
+ master.connect(useRtuOverTcp);
+ }
+ catch (Exception e) {
+ // Do nothing.
+ }
+ }
+ throw new ModbusIOException("I/O socket exception - failed to write - %s", ex1.getMessage());
+ }
+ catch (Exception ex2) {
+ throw new ModbusIOException("General exception - failed to write - %s", ex2.getMessage());
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java
new file mode 100644
index 0000000..12c7a93
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusTransaction.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusException;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+
+import java.util.Random;
+
+/**
+ * Interface defining a ModbusTransaction.
+ *
+ * A transaction is defined by the sequence of
+ * sending a request message and receiving a
+ * related response message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class ModbusTransaction {
+
+ protected AbstractModbusTransport transport;
+ protected ModbusRequest request;
+ protected ModbusResponse response;
+ boolean validityCheck = Modbus.DEFAULT_VALIDITYCHECK;
+ int retries = Modbus.DEFAULT_RETRIES;
+ private Random random = new Random(System.nanoTime());
+ static int transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+
+ /**
+ * Returns the ModbusRequest instance
+ * associated with this ModbusTransaction.
+ *
+ *
+ * @return the associated ModbusRequest instance.
+ */
+ public ModbusRequest getRequest() {
+ return request;
+ }
+
+ /**
+ * Sets the ModbusRequest for this
+ * ModbusTransaction.
+ * The related ModbusResponse is acquired
+ * from the passed in ModbusRequest instance.
+ *
+ * @param req a ModbusRequest.
+ */
+ public void setRequest(ModbusRequest req) {
+ request = req;
+ if (req != null) {
+ request.setTransactionID(getTransactionID());
+ }
+ }
+
+ /**
+ * Returns the ModbusResponse instance
+ * associated with this ModbusTransaction.
+ *
+ *
+ * @return the associated ModbusRequest instance.
+ */
+ public ModbusResponse getResponse() {
+ return response;
+ }
+
+ /**
+ * Returns the amount of retries for opening
+ * the connection for executing the transaction.
+ *
+ *
+ * @return the amount of retries as int.
+ */
+ int getRetries() {
+ return retries;
+ }
+
+ /**
+ * Set the amount of retries for opening
+ * the connection for executing the transaction.
+ *
+ *
+ * @param retries the amount of retries as int.
+ */
+ public void setRetries(int retries) {
+ this.retries = retries;
+ }
+
+ /**
+ * Tests whether the validity of a transaction
+ * will be checked.
+ *
+ *
+ * @return true if checking validity, false otherwise.
+ */
+ public boolean isCheckingValidity() {
+ return validityCheck;
+ }
+
+ /**
+ * Sets the flag that controls whether the
+ * validity of a transaction will be checked.
+ *
+ *
+ * @param b true if checking validity, false otherwise.
+ */
+ public void setCheckingValidity(boolean b) {
+ validityCheck = b;
+ }
+
+ /**
+ * getTransactionID -- get the next transaction ID to use.
+ * @return next transaction ID to use
+ */
+ synchronized public int getTransactionID() {
+ /*
+ * Ensure that the transaction ID is in the valid range between
+ * 0 and MAX_TRANSACTION_ID (65534). If not, the value will be forced
+ * to 0.
+ */
+ if (transactionID < Modbus.DEFAULT_TRANSACTION_ID && isCheckingValidity()) {
+ transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+ }
+ if (transactionID >= Modbus.MAX_TRANSACTION_ID) {
+ transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+ }
+ return transactionID;
+ }
+
+ /**
+ * A useful method for getting a random sleep time based on an increment of the retry count and retry sleep time
+ *
+ * @param count Retry count
+ * @return Random sleep time in milliseconds
+ */
+ long getRandomSleepTime(int count) {
+ return (Modbus.RETRY_SLEEP_TIME / 2) + (long) (random.nextDouble() * Modbus.RETRY_SLEEP_TIME * count);
+ }
+
+ /**
+ * Executes this ModbusTransaction.
+ * Locks the ModbusTransport for sending
+ * the ModbusRequest and reading the
+ * related ModbusResponse.
+ * If reconnecting is activated the connection will
+ * be opened for the transaction and closed afterwards.
+ *
+ *
+ * @throws ModbusException if an I/O error occurs,
+ * or the response is a modbus protocol exception.
+ */
+ public abstract void execute() throws ModbusException;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransaction.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransaction.java
new file mode 100644
index 0000000..1ca212c
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransaction.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusException;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.ModbusSlaveException;
+import com.ghgande.j2mod.modbus.msg.ExceptionResponse;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.net.AbstractUDPTerminal;
+import com.ghgande.j2mod.modbus.net.UDPMasterConnection;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class implementing the ModbusTransaction
+ * interface for the UDP transport mechanism.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusUDPTransaction extends ModbusTransaction {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusUDPTransaction.class);
+
+ //instance attributes and associations
+ private AbstractUDPTerminal terminal;
+ private final Object MUTEX = new Object();
+
+ /**
+ * Constructs a new ModbusUDPTransaction
+ * instance.
+ */
+ public ModbusUDPTransaction() {
+ }
+
+ /**
+ * Constructs a new ModbusUDPTransaction
+ * instance with a given ModbusRequest to
+ * be send when the transaction is executed.
+ *
+ *
+ * @param request a ModbusRequest instance.
+ */
+ public ModbusUDPTransaction(ModbusRequest request) {
+ setRequest(request);
+ }
+
+ /**
+ * Constructs a new ModbusUDPTransaction
+ * instance with a given UDPTerminal to
+ * be used for transactions.
+ *
+ *
+ * @param terminal a UDPTerminal instance.
+ */
+ public ModbusUDPTransaction(AbstractUDPTerminal terminal) {
+ setTerminal(terminal);
+ }
+
+ /**
+ * Constructs a new ModbusUDPTransaction
+ * instance with a given ModbusUDPConnection
+ * to be used for transactions.
+ *
+ *
+ * @param con a ModbusUDPConnection instance.
+ */
+ public ModbusUDPTransaction(UDPMasterConnection con) {
+ setTerminal(con.getTerminal());
+ }
+
+ /**
+ * Sets the terminal on which this ModbusTransaction
+ * should be executed.
+ *
+ * @param terminal a UDPSlaveTerminal.
+ */
+ public void setTerminal(AbstractUDPTerminal terminal) {
+ this.terminal = terminal;
+ if (terminal.isActive()) {
+ transport = terminal.getTransport();
+ }
+ }
+
+ @Override
+ public void execute() throws ModbusIOException, ModbusSlaveException, ModbusException {
+
+ //1. assert executeability
+ assertExecutable();
+ //2. open the connection if not connected
+ if (!terminal.isActive()) {
+ try {
+ terminal.activate();
+ transport = terminal.getTransport();
+ }
+ catch (Exception ex) {
+ logger.debug("Terminal activation failed.", ex);
+ throw new ModbusIOException("Activation failed");
+ }
+ }
+
+ //3. Retry transaction retries times, in case of
+ //I/O Exception problems.
+ int retryCount = 0;
+ while (retryCount <= retries) {
+ try {
+ //3. write request, and read response,
+ // while holding the lock on the IO object
+ synchronized (MUTEX) {
+ //write request message
+ transport.writeRequest(request);
+ //read response message
+ response = transport.readResponse();
+ break;
+ }
+ }
+ catch (ModbusIOException ex) {
+ retryCount++;
+ if (retryCount > retries) {
+ logger.error("Cannot send UDP message", ex);
+ }
+ else {
+ ModbusUtil.sleep(getRandomSleepTime(retryCount));
+ }
+ }
+ }
+
+ //4. deal with "application level" exceptions
+ if (response instanceof ExceptionResponse) {
+ throw new ModbusSlaveException(((ExceptionResponse)response).getExceptionCode());
+ }
+
+ //toggle the id
+ incrementTransactionID();
+ }
+
+ /**
+ * Asserts if this ModbusTCPTransaction is
+ * executable.
+ *
+ * @throws ModbusException if this transaction cannot be
+ * asserted as executable.
+ */
+ private void assertExecutable() throws ModbusException {
+ if (request == null || terminal == null) {
+ throw new ModbusException("Assertion failed, transaction not executable");
+ }
+ }
+
+ /**
+ * Toggles the transaction identifier, to ensure
+ * that each transaction has a distinctive
+ * identifier.
+ *
+ * @param terminal the UDPTerminal used for message transport.
+ */
+ public ModbusUDPTransport(AbstractUDPTerminal terminal) {
+ this.terminal = terminal;
+ }
+
+ @Override
+ public void setTimeout(int time) {
+ super.setTimeout(time);
+ if (terminal != null) {
+ terminal.setTimeout(timeout);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+
+ @Override
+ public ModbusTransaction createTransaction() {
+ ModbusUDPTransaction trans = new ModbusUDPTransaction();
+ trans.setTerminal(terminal);
+ return trans;
+ }
+
+ @Override
+ public void writeResponse(ModbusResponse msg) throws ModbusIOException {
+ writeMessage(msg);
+ }
+
+ @Override
+ public void writeRequest(ModbusRequest msg) throws ModbusIOException {
+ writeMessage(msg);
+ }
+
+ @Override
+ public ModbusRequest readRequest(AbstractModbusListener listener) throws ModbusIOException {
+ try {
+ ModbusRequest req;
+ synchronized (byteInputStream) {
+ byteInputStream.reset(terminal.receiveMessage());
+ byteInputStream.skip(7);
+ int functionCode = byteInputStream.readUnsignedByte();
+ byteInputStream.reset();
+ req = ModbusRequest.createModbusRequest(functionCode);
+ req.readFrom(byteInputStream);
+ }
+ return req;
+ }
+ catch (Exception ex) {
+ throw new ModbusIOException("I/O exception - failed to read", ex);
+ }
+ }
+
+ @Override
+ public ModbusResponse readResponse() throws ModbusIOException {
+
+ try {
+ ModbusResponse res;
+ synchronized (byteInputStream) {
+ byteInputStream.reset(terminal.receiveMessage());
+ byteInputStream.skip(7);
+ int functionCode = byteInputStream.readUnsignedByte();
+ byteInputStream.reset();
+ res = ModbusResponse.createModbusResponse(functionCode);
+ res.readFrom(byteInputStream);
+ }
+ return res;
+ }
+ catch (InterruptedIOException ioex) {
+ throw new ModbusIOException("Socket was interrupted", ioex);
+ }
+ catch (Exception ex) {
+ logger.debug("I/O exception while reading modbus response.", ex);
+ throw new ModbusIOException("I/O exception - failed to read - %s", ex.getMessage());
+ }
+ }
+
+ /**
+ * Writes the request/response message to the port
+ * @param msg Message to write
+ * @throws ModbusIOException If the port cannot be written to
+ */
+ private void writeMessage(ModbusMessage msg) throws ModbusIOException {
+ try {
+ synchronized (byteOutputStream) {
+ int len = msg.getOutputLength();
+ byteOutputStream.reset();
+ msg.writeTo(byteOutputStream);
+ byte data[] = byteOutputStream.getBuffer();
+ data = Arrays.copyOf(data, len);
+ terminal.sendMessage(data);
+ }
+ }
+ catch (Exception ex) {
+ throw new ModbusIOException("I/O exception - failed to write", ex);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/NonWordDataHandler.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/NonWordDataHandler.java
new file mode 100644
index 0000000..29fc3a5
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/NonWordDataHandler.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import java.io.DataInput;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Interface implementing a non word data handler for the read/write multiple
+ * register commands.
+ *
+ * This interface can be used by any class which works with multiple words of
+ * data for a non-standard data item. For example, message may involve data
+ * items which are floating point values or string.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ * @deprecated In the interests of keeping the library simple, this will be removed in a future release
+ */
+@Deprecated
+public interface NonWordDataHandler {
+
+ /**
+ * Returns the intermediate raw non-word data.
+ *
+ *
+ * An implementation would need to provide a means of converting between the
+ * raw byte data and the registers that are present in actual messages.
+ *
+ * @return the raw data as byte[].
+ */
+ byte[] getData();
+
+ /**
+ * Reads the non-word raw data based on an arbitrary implemented structure.
+ *
+ * @param in the DataInput to read from.
+ * @param reference to specify the offset as int.
+ * @param count to specify the amount of bytes as int.
+ *
+ * @throws IOException if I/O fails.
+ * @throws EOFException if the stream ends before all data is read.
+ */
+ void readData(DataInput in, int reference, int count) throws IOException, EOFException;
+
+ /**
+ * Returns the word count of the data. Note that this should be the length
+ * of the byte array divided by two.
+ *
+ * @return the number of words the data consists of.
+ */
+ int getWordCount();
+
+ /**
+ * Commits the data if it has been read into an intermediate repository.
+ *
+ *
+ * This method is called for a message (for example, a
+ * WriteMultipleRegistersRequest instance) when finished with
+ * reading, for creating a response.
+ *
+ * @return -1 if the commit was successful, a Modbus exception code valid
+ * for the read/write multiple registers commands otherwise.
+ */
+ int commitUpdate();
+
+ /**
+ * Prepares the raw data, putting it together from a backing data store.
+ *
+ *
+ * This method is called for a message (for example, * ReadMultipleRegistersRequest) when finished with reading, for
+ * creating a response.
+ *
+ * @param reference to specify the offset as int.
+ * @param count to specify the number of bytes as int.
+ */
+ void prepareData(int reference, int count);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ExceptionResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ExceptionResponse.java
new file mode 100644
index 0000000..b599327
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ExceptionResponse.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing aModbusResponse that represents an exception.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ExceptionResponse extends ModbusResponse {
+
+ // instance attributes
+ private int exceptionCode = -1;
+
+ /**
+ * Constructs a new ExceptionResponse instance with a given
+ * function code and an exception code. The function code will be
+ * automatically increased with the exception offset.
+ *
+ * @param fc the function code as int.
+ * @param exc the exception code as int.
+ */
+ public ExceptionResponse(int fc, int exc) {
+
+ // One byte of data.
+ setDataLength(1);
+ setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
+
+ exceptionCode = exc;
+ }
+
+ /**
+ * Constructs a new ExceptionResponse instance with a given
+ * function code. ORs the exception offset automatically.
+ *
+ * @param fc the function code as int.
+ */
+ public ExceptionResponse(int fc) {
+
+ // One byte of data.
+ setDataLength(1);
+ setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
+ }
+
+ /**
+ * Constructs a new ExceptionResponse instance with no function
+ * or exception code.
+ */
+ public ExceptionResponse() {
+
+ // One byte of data.
+ setDataLength(1);
+ }
+
+ /**
+ * Returns the Modbus exception code of this ExceptionResponse.
+ *
+ *
+ * @return the exception code as int.
+ */
+ public int getExceptionCode() {
+ return exceptionCode;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(getExceptionCode());
+ }
+
+ /**
+ * readData()
+ *
+ * read the single byte of data, which is the exception code.
+ * @throws IOException If the data cannot be read from the socket/port
+ */
+ public void readData(DataInput din) throws IOException {
+ exceptionCode = din.readUnsignedByte();
+ }
+
+ /**
+ * getMessage()
+ *
+ * return the exception type, which is the "message" for this response.
+ *
+ * @return -- byte array containing the 1 byte exception code.
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[1];
+ result[0] = (byte)getExceptionCode();
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalAddressExceptionResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalAddressExceptionResponse.java
new file mode 100644
index 0000000..a61456d
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalAddressExceptionResponse.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+/**
+ * @author Julie
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class IllegalAddressExceptionResponse extends ExceptionResponse {
+
+ /**
+ *
+ */
+ public IllegalAddressExceptionResponse() {
+ super(0, Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ public IllegalAddressExceptionResponse(int function) {
+ super(function, Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ /**
+ * Sets the function code
+ * @param fc Function code
+ */
+ public void setFunctionCode(int fc) {
+ super.setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionExceptionResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionExceptionResponse.java
new file mode 100644
index 0000000..4f9ce42
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionExceptionResponse.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+/**
+ * @author jfhaugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class IllegalFunctionExceptionResponse extends ExceptionResponse {
+
+ /**
+ *
+ */
+ public IllegalFunctionExceptionResponse() {
+ super(0, Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ public IllegalFunctionExceptionResponse(int function) {
+ super(function | Modbus.EXCEPTION_OFFSET, Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * Sets the function code
+ * @param fc Function code
+ */
+ public void setFunctionCode(int fc) {
+ super.setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionRequest.java
new file mode 100644
index 0000000..33503d3
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalFunctionRequest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ *
+ * Class implementing a ModbusRequest which is created for illegal or
+ * non implemented function codes.
+ *
+ *
+ * This is just a helper class to keep the implementation patterns the same for
+ * all cases.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class IllegalFunctionRequest extends ModbusRequest {
+
+ /**
+ * Constructs a new IllegalFunctionRequest instance for a given
+ * function code.
+ *
+ * Used to implement slave devices when an illegal function code
+ * has been requested.
+ *
+ * @param function the function code as int.
+ */
+ public IllegalFunctionRequest(int function) {
+ setFunctionCode(function);
+ }
+
+ /**
+ * Constructs a new IllegalFunctionRequest instance for a given
+ * function code.
+ *
+ * Used to implement slave devices when an illegal function code
+ * has been requested.
+ *
+ * @param unit Unit ID
+ * @param function the function code as int.
+ */
+ public IllegalFunctionRequest(int unit, int function) {
+ setUnitID(unit);
+ setFunctionCode(function);
+ }
+
+ /**
+ * There is no unit number associated with this exception.
+ * @return Modbus excepion response
+ */
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new IllegalFunctionExceptionResponse(getFunctionCode()), true);
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ throw new RuntimeException();
+ }
+
+ /**
+ * Read all of the data that can be read. This is an unsupported
+ * function, so it may not be possible to know exactly how much data
+ * needs to be read.
+ * @throws IOException If the data cannot be read from the socket/port
+ */
+ public void readData(DataInput din) throws IOException {
+ // skip all following bytes
+ int length = getDataLength();
+ for (int i = 0; i < length; i++) {
+ din.readByte();
+ }
+ }
+
+ public byte[] getMessage() {
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalValueExceptionResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalValueExceptionResponse.java
new file mode 100644
index 0000000..05549e0
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/IllegalValueExceptionResponse.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+/**
+ * @author Julie
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class IllegalValueExceptionResponse extends ExceptionResponse {
+
+ /**
+ *
+ */
+ public IllegalValueExceptionResponse() {
+ super(0, Modbus.ILLEGAL_VALUE_EXCEPTION);
+ }
+
+ public IllegalValueExceptionResponse(int function) {
+ super(function, Modbus.ILLEGAL_VALUE_EXCEPTION);
+ }
+
+ /**
+ * Sets the function code
+ * @param fc Function code
+ */
+ public void setFunctionCode(int fc) {
+ super.setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterRequest.java
new file mode 100644
index 0000000..65794c1
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterRequest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.Register;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Mask Write Register request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class MaskWriteRegisterRequest extends ModbusRequest {
+ private int reference;
+ private int andMask;
+ private int orMask;
+
+ /**
+ * Constructs a new Mask Write Register request.
+ *
+ * @param ref Register
+ * @param andMask AND Mask to use
+ * @param orMask OR Mask to use
+ */
+ public MaskWriteRegisterRequest(int ref, int andMask, int orMask) {
+ super();
+
+ setFunctionCode(Modbus.MASK_WRITE_REGISTER);
+ setReference(ref);
+ setAndMask(andMask);
+ setOrMask(orMask);
+
+ setDataLength(6);
+ }
+
+ /**
+ * Constructs a new Mask Write Register request.
+ * instance.
+ */
+ public MaskWriteRegisterRequest() {
+ super();
+
+ setFunctionCode(Modbus.MASK_WRITE_REGISTER);
+ setDataLength(6);
+ }
+
+ /**
+ * getReference
+ * @return the reference field
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference -- set the reference field.
+ * @param ref the reference field
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * getAndMask -- return the AND mask value;
+ *
+ * @return int
+ */
+ public int getAndMask() {
+ return andMask;
+ }
+
+ /**
+ * setAndMask -- set AND mask
+ * @param mask AND mask
+ */
+ public void setAndMask(int mask) {
+ andMask = mask;
+ }
+
+ /**
+ * getOrMask -- return the OR mask value;
+ *
+ * @return int
+ */
+ public int getOrMask() {
+ return orMask;
+ }
+
+ /**
+ * setOrMask -- set OR mask
+ * @param mask OR mask
+ */
+ public void setOrMask(int mask) {
+ orMask = mask;
+ }
+
+ /**
+ * getResponse -- create an empty response for this request.
+ * @return empty response for this request
+ */
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new MaskWriteRegisterResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ MaskWriteRegisterResponse response;
+
+ // Get the process image.
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ try {
+ Register register = procimg.getRegister(reference);
+
+ /*
+ * Get the original value. The AND mask will first be
+ * applied to clear any bits, then the OR mask will be
+ * applied to set them.
+ */
+ int value = register.getValue();
+ value = (value & andMask) | (orMask & ~andMask);
+
+ // Store the modified value back where it came from.
+ register.setValue(value);
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (MaskWriteRegisterResponse)getResponse();
+ response.setReference(reference);
+ response.setAndMask(andMask);
+ response.setOrMask(orMask);
+
+ return response;
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written from the socket/port
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- dummy function. There is no data with the request.
+ * @throws IOException If the data cannot be read from the socket/port
+ */
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ andMask = din.readUnsignedShort();
+ orMask = din.readUnsignedShort();
+ }
+
+ /**
+ * getMessage -- return an empty array as there is no data for
+ * this request.
+ * @return message payload
+ */
+ public byte[] getMessage() {
+ byte results[] = new byte[6];
+
+ results[0] = (byte)(reference >> 8);
+ results[1] = (byte)(reference & 0xFF);
+ results[2] = (byte)(andMask >> 8);
+ results[3] = (byte)(andMask & 0xFF);
+ results[4] = (byte)(orMask >> 8);
+ results[5] = (byte)(orMask & 0xFF);
+
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterResponse.java
new file mode 100644
index 0000000..f22cb5f
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/MaskWriteRegisterResponse.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadMEIResponse.
+ *
+ * Derived from similar class for Read Coils response.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class MaskWriteRegisterResponse
+ extends ModbusResponse {
+
+ // Message fields.
+ private int reference;
+ private int andMask;
+ private int orMask;
+
+ /**
+ * Constructs a new ReportSlaveIDResponse
+ * instance.
+ */
+ public MaskWriteRegisterResponse() {
+ super();
+ setFunctionCode(Modbus.MASK_WRITE_REGISTER);
+ }
+
+ /**
+ * getReference
+ * @return the reference field
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference -- set the reference field.
+ * @param ref Register value
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * getAndMask -- return the AND mask value;
+ *
+ * @return int
+ */
+ public int getAndMask() {
+ return andMask;
+ }
+
+ /**
+ * setAndMask -- set AND mask
+ * @param mask Mask to use
+ */
+ public void setAndMask(int mask) {
+ andMask = mask;
+ }
+
+ /**
+ * getOrMask -- return the OR mask value;
+ *
+ * @return int
+ */
+ public int getOrMask() {
+ return orMask;
+ }
+
+ /**
+ * setOrMask -- set OR mask
+ * @param mask OR bit mask
+ */
+ public void setOrMask(int mask) {
+ orMask = mask;
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written to the socket/port
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a
+ * header, such as for Modbus/TCP, it will have been read
+ * already.
+ * @throws IOException If the data cannot be read from the socket/port
+ */
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ andMask = din.readUnsignedShort();
+ orMask = din.readUnsignedShort();
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Byte array of the message
+ */
+ public byte[] getMessage() {
+ byte results[] = new byte[6];
+
+ results[0] = (byte)(reference >> 8);
+ results[1] = (byte)(reference & 0xFF);
+ results[2] = (byte)(andMask >> 8);
+ results[3] = (byte)(andMask & 0xFF);
+ results[4] = (byte)(orMask >> 8);
+ results[5] = (byte)(orMask & 0xFF);
+
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessage.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessage.java
new file mode 100644
index 0000000..be5693b
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessage.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Interface defining a ModbusMessage.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface ModbusMessage {
+
+ /**
+ * Check the flag which indicates that this ModbusMessage is for a
+ * headless (serial, or headless networked) connection.
+ * @return is for a headless (serial, or headless networked) connection
+ */
+ boolean isHeadless();
+
+ /**
+ * Sets the flag that marks this ModbusMessage as headless (for
+ * serial transport).
+ */
+ void setHeadless();
+
+ /**
+ * Returns the transaction identifier of this ModbusMessage as
+ * int.
+ *
+ *
+ * The identifier is a 2-byte (short) non negative integer value valid in
+ * the range of 0-65535.
+ *
+ * @return the transaction identifier as int.
+ */
+ int getTransactionID();
+
+ /**
+ * Returns the protocol identifier of this ModbusMessage as
+ * int.
+ *
+ *
+ * The identifier is a 2-byte (short) non negative integer value valid in
+ * the range of 0-65535.
+ *
+ * @return the protocol identifier as int.
+ */
+ int getProtocolID();
+
+ /**
+ * Returns the length of the data appended after the protocol header.
+ *
+ *
+ * @return the data length as int.
+ */
+ int getDataLength();
+
+ /**
+ * Returns the unit identifier of this ModbusMessage as
+ * int.
+ *
+ *
+ * The identifier is a 1-byte non negative integer value valid in the range
+ * of 0-255.
+ *
+ * @return the unit identifier as int.
+ */
+ int getUnitID();
+
+ /**
+ * Returns the function code of this ModbusMessage as int.
+ * Function codes are ordered in conformance classes their values are
+ * specified in com.ghgande.j2mod.modbus.Modbus.
+ *
+ * @return the function code as int.
+ *
+ * @see com.ghgande.j2mod.modbus.Modbus
+ */
+ int getFunctionCode();
+
+ /**
+ * Returns the raw message as an array of bytes.
+ *
+ *
+ * @return the raw message as byte[].
+ */
+ byte[] getMessage();
+
+ /**
+ * Returns the raw message as String containing a
+ * hexadecimal series of bytes.
+ *
+ *
+ * This method is specially for debugging purposes, allowing the user to log
+ * the communication in a manner used in the specification document.
+ *
+ * @return the raw message as String containing a
+ * hexadecimal series of bytes.
+ */
+ String getHexMessage();
+
+ /**
+ * Returns the number of bytes that will
+ * be written by {@link #writeTo(DataOutput)}.
+ *
+ * @return the number of bytes that will be written as int.
+ */
+ int getOutputLength();
+
+ /**
+ * Writes this Transportable to the
+ * given DataOutput.
+ *
+ * @param dout the DataOutput to write to.
+ *
+ * @throws IOException if an I/O error occurs.
+ */
+ void writeTo(DataOutput dout) throws IOException;
+
+ /**
+ * Reads this Transportable from the given
+ * DataInput.
+ *
+ * @param din the DataInput to read from.
+ *
+ * @throws IOException if an I/O error occurs or the data
+ * is invalid.
+ */
+ void readFrom(DataInput din) throws IOException;
+
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessageImpl.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessageImpl.java
new file mode 100644
index 0000000..f9ebe20
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusMessageImpl.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Abstract class implementing a ModbusMessage. This class provides
+ * specialised implementations with the functionality they have in common.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class ModbusMessageImpl implements ModbusMessage {
+
+ // instance attributes
+ private int transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+ private int protocolID = Modbus.DEFAULT_PROTOCOL_ID;
+ private int dataLength;
+ private int unitID = Modbus.DEFAULT_UNIT_ID;
+ private int functionCode;
+ private boolean headless = false; // flag for header-less (serial)
+
+ @Override
+ public boolean isHeadless() {
+ return headless;
+ }
+
+ @Override
+ public void setHeadless() {
+ headless = true;
+ }
+
+ @Override
+ public int getTransactionID() {
+ return transactionID & 0x0000FFFF;
+ }
+
+ /**
+ * Sets the transaction identifier of this ModbusMessage.
+ *
+ *
+ * The identifier must be a 2-byte (short) non negative integer value valid
+ * in the range of 0-65535.
+ * The identifier should be a 2-byte (short) non negative integer value
+ * valid in the range of 0-65535.
+ *
+ * @param pid the protocol identifier as int.
+ */
+ public void setProtocolID(int pid) {
+ protocolID = pid;
+ }
+
+ @Override
+ public int getDataLength() {
+ return dataLength;
+ }
+
+ /**
+ * Sets the length of the data appended after the protocol header.
+ *
+ *
+ * Note that this library, a bit in contrast to the specification, counts
+ * the unit identifier and the function code in the header, because it is
+ * part of each and every message. Thus this method will add two (2) to the
+ * passed in integer value.
+ *
+ *
+ * This method does not include the length of a final CRC/LRC for those
+ * protocols which requirement.
+ *
+ * @param length the data length as int.
+ */
+ public void setDataLength(int length) {
+ if (length < 0 || length + 2 > 255) {
+ throw new IllegalArgumentException("Invalid length: " + length);
+ }
+
+ dataLength = length + 2;
+ }
+
+ @Override
+ public int getUnitID() {
+ return unitID;
+ }
+
+ /**
+ * Sets the unit identifier of this ModbusMessage.
+ * The response must include the unit number, function code, as well as any
+ * transport-specific header information.
+ *
+ *
+ * This method is used to create an empty response which must be populated
+ * by the caller. It is commonly used to un-marshal responses from Modbus
+ * slaves.
+ *
+ * @return the corresponding ModbusResponse.
+ */
+ public abstract ModbusResponse getResponse();
+
+ /**
+ * Returns the ModbusResponse that represents the answer to this
+ * ModbusRequest.
+ *
+ *
+ * The implementation should take care about assembling the reply to this
+ * ModbusRequest.
+ *
+ *
+ * This method is used to create responses from the process image associated
+ * with the listener. It is commonly used to implement Modbus slave
+ * instances.
+ *
+ * @param listener Listener that received the request
+ * @return the corresponding ModbusResponse.
+ */
+ public abstract ModbusResponse createResponse(AbstractModbusListener listener);
+
+ /**
+ * Factory method for creating exception responses with the given exception
+ * code.
+ *
+ * @param code the code of the exception.
+ *
+ * @return a ModbusResponse instance representing the exception response.
+ */
+ public ModbusResponse createExceptionResponse(int code) {
+ return updateResponseWithHeader(new ExceptionResponse(getFunctionCode(), code), true);
+ }
+
+ /**
+ * Updates the response with the header information to match the request
+ *
+ * @param response Response to update
+ * @return Updated response
+ */
+ ModbusResponse updateResponseWithHeader(ModbusResponse response) {
+ return updateResponseWithHeader(response, false);
+ }
+
+ /**
+ * Updates the response with the header information to match the request
+ *
+ * @param response Response to update
+ * @param ignoreFunctionCode True if the function code should stay unmolested
+ * @return Updated response
+ */
+ ModbusResponse updateResponseWithHeader(ModbusResponse response, boolean ignoreFunctionCode) {
+
+ // transfer header data
+ response.setHeadless(isHeadless());
+ if (!isHeadless()) {
+ response.setTransactionID(getTransactionID());
+ response.setProtocolID(getProtocolID());
+ }
+ else {
+ response.setHeadless();
+ }
+ response.setUnitID(getUnitID());
+ if (!ignoreFunctionCode) {
+ response.setFunctionCode(getFunctionCode());
+ }
+ return response;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusResponse.java
new file mode 100644
index 0000000..41d2cea
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusResponse.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import static com.ghgande.j2mod.modbus.msg.ModbusResponse.AuxiliaryMessageTypes.NONE;
+
+/**
+ * Abstract class implementing a ModbusResponse. This class provides
+ * specialised implementations with the functionality they have in common.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class ModbusResponse extends ModbusMessageImpl {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusResponse.class);
+
+ public enum AuxiliaryMessageTypes {
+ NONE, UNIT_ID_MISSMATCH
+ }
+ private AuxiliaryMessageTypes auxiliaryType = NONE;
+
+ /**
+ * Factory method creating the required specialized ModbusResponse
+ * instance.
+ *
+ * @param functionCode the function code of the response as int.
+ *
+ * @return a ModbusResponse instance specific for the given function code.
+ */
+ public static ModbusResponse createModbusResponse(int functionCode) {
+ ModbusResponse response;
+
+ switch (functionCode) {
+ case Modbus.READ_COILS:
+ response = new ReadCoilsResponse();
+ break;
+ case Modbus.READ_INPUT_DISCRETES:
+ response = new ReadInputDiscretesResponse();
+ break;
+ case Modbus.READ_MULTIPLE_REGISTERS:
+ response = new ReadMultipleRegistersResponse();
+ break;
+ case Modbus.READ_INPUT_REGISTERS:
+ response = new ReadInputRegistersResponse();
+ break;
+ case Modbus.WRITE_COIL:
+ response = new WriteCoilResponse();
+ break;
+ case Modbus.WRITE_SINGLE_REGISTER:
+ response = new WriteSingleRegisterResponse();
+ break;
+ case Modbus.WRITE_MULTIPLE_COILS:
+ response = new WriteMultipleCoilsResponse();
+ break;
+ case Modbus.WRITE_MULTIPLE_REGISTERS:
+ response = new WriteMultipleRegistersResponse();
+ break;
+ case Modbus.READ_EXCEPTION_STATUS:
+ response = new ReadExceptionStatusResponse();
+ break;
+ case Modbus.READ_SERIAL_DIAGNOSTICS:
+ response = new ReadSerialDiagnosticsResponse();
+ break;
+ case Modbus.READ_COMM_EVENT_COUNTER:
+ response = new ReadCommEventCounterResponse();
+ break;
+ case Modbus.READ_COMM_EVENT_LOG:
+ response = new ReadCommEventLogResponse();
+ break;
+ case Modbus.REPORT_SLAVE_ID:
+ response = new ReportSlaveIDResponse();
+ break;
+ case Modbus.READ_FILE_RECORD:
+ response = new ReadFileRecordResponse();
+ break;
+ case Modbus.WRITE_FILE_RECORD:
+ response = new WriteFileRecordResponse();
+ break;
+ case Modbus.MASK_WRITE_REGISTER:
+ response = new MaskWriteRegisterResponse();
+ break;
+ case Modbus.READ_WRITE_MULTIPLE:
+ response = new ReadWriteMultipleResponse();
+ break;
+ case Modbus.READ_FIFO_QUEUE:
+ response = new ReadFIFOQueueResponse();
+ break;
+ case Modbus.READ_MEI:
+ response = new ReadMEIResponse();
+ break;
+ default:
+ if ((functionCode & 0x80) != 0) {
+ response = new ExceptionResponse(functionCode);
+ }
+ else {
+ response = new ExceptionResponse();
+ }
+ break;
+ }
+ return response;
+ }
+
+ /**
+ * Utility method to set the raw data of the message. Should not be used
+ * except under rare circumstances.
+ *
+ *
+ * @param msg the byte[] resembling the raw modbus response
+ * message.
+ */
+ protected void setMessage(byte[] msg) {
+ try {
+ readData(new DataInputStream(new ByteArrayInputStream(msg)));
+ }
+ catch (IOException ex) {
+ logger.error("Problem setting response message - {}", ex.getMessage());
+ }
+ }
+
+ /**
+ * Returns the auxiliary type of this response message
+ * Useful for adding extra information to the message that can be used by downstream processing
+ *
+ * @return Auxiliary type
+ */
+ public AuxiliaryMessageTypes getAuxiliaryType() {
+ return auxiliaryType;
+ }
+
+ /**
+ * Sets the auxiliary type of this response
+ *
+ * @param auxiliaryType Type
+ */
+ public void setAuxiliaryType(AuxiliaryMessageTypes auxiliaryType) {
+ this.auxiliaryType = auxiliaryType;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsRequest.java
new file mode 100644
index 0000000..b8bd207
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsRequest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.DigitalOut;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadCoilsRequest. The implementation directly
+ * correlates with the class 1 function read coils (FC 1). It
+ * encapsulates the corresponding request message.
+ *
+ *
+ * Coils are understood as bits that can be manipulated (i.e. set or unset).
+ *
+ * @author Dieter Wimberger
+ * @author jfhaugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCoilsRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private int bitCount;
+
+ /**
+ * Constructs a new ReadCoilsRequest instance.
+ */
+ public ReadCoilsRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_COILS);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new ReadCoilsRequest instance with a given
+ * reference and count of coils (i.e. bits) to be read.
+ *
+ *
+ * @param ref the reference number of the register to read from.
+ * @param count the number of bits to be read.
+ */
+ public ReadCoilsRequest(int ref, int count) {
+ super();
+
+ setFunctionCode(Modbus.READ_COILS);
+ setDataLength(4);
+
+ setReference(ref);
+ setBitCount(count);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadCoilsResponse(bitCount));
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ModbusResponse response;
+ DigitalOut[] douts;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ // 2. get input discretes range
+ try {
+ douts = procimg.getDigitalOutRange(getReference(), getBitCount());
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = getResponse();
+
+ // Populate the discrete values from the process image.
+ for (int i = 0; i < douts.length; i++) {
+ ((ReadCoilsResponse)response).setCoilStatus(i, douts[i].isSet());
+ }
+
+ return response;
+ }
+
+ /**
+ * Returns the reference of the register to to start reading from with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @return the reference of the register to start reading from as
+ * int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to start reading from with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @param ref the reference of the register to start reading from.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the number of bits (i.e. coils) to be read with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @return the number of bits to be read.
+ */
+ public int getBitCount() {
+ return bitCount;
+ }
+
+ /**
+ * Sets the number of bits (i.e. coils) to be read with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @param count the number of bits to be read.
+ */
+ public void setBitCount(int count) {
+ if (count > Modbus.MAX_BITS) {
+ throw new IllegalArgumentException("Maximum bitcount exceeded");
+ }
+ else {
+ bitCount = count;
+ }
+ }
+
+ @Override
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ @Override
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ bitCount = din.readUnsignedShort();
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)((reference & 0xff));
+ result[2] = (byte)((bitCount >> 8) & 0xff);
+ result[3] = (byte)((bitCount & 0xff));
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsResponse.java
new file mode 100644
index 0000000..33bad4d
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCoilsResponse.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.util.BitVector;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadCoilsResponse.
+ * The implementation directly correlates with the class 1
+ * function read coils (FC 1). It encapsulates
+ * the corresponding response message.
+ *
+ * Coils are understood as bits that can be manipulated
+ * (i.e. set or unset).
+ *
+ * @author Dieter Wimberger
+ * @version 1.2rc1 (09/11/2004)
+ */
+
+/**
+ * Completed re-implementation 1/10/2011
+ *
+ * Created getMessage() method to abstractly create the message
+ * data.
+ * Cleaned up the constructors.
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCoilsResponse extends ModbusResponse {
+ private BitVector coils;
+
+ /**
+ * ReadCoilsResponse -- create an empty response message to be
+ * filled in later.
+ */
+ public ReadCoilsResponse() {
+ setFunctionCode(Modbus.READ_COILS);
+ setDataLength(1);
+ coils = null;
+ }
+
+ /**
+ * ReadCoilsResponse -- create a response for a given number of
+ * coils.
+ *
+ * @param count the number of bits to be read.
+ */
+ public ReadCoilsResponse(int count) {
+ setFunctionCode(Modbus.READ_COILS);
+ coils = new BitVector(count);
+ setDataLength(coils.byteSize() + 1);
+ }
+
+ /**
+ * getBitCount -- return the number of coils
+ *
+ * @return number of defined coils
+ */
+ public int getBitCount() {
+ if (coils == null) {
+ return 0;
+ }
+ else {
+ return coils.size();
+ }
+ }
+
+ /**
+ * getCoils -- get the coils bit vector.
+ *
+ * The coils vector may be read (when operating as a master) or
+ * written (when operating as a slave).
+ *
+ * @return BitVector containing the coils.
+ */
+ public BitVector getCoils() {
+ return coils;
+ }
+
+ /**
+ * Convenience method that returns the state
+ * of the bit at the given index.
+ *
+ *
+ * @param index the index of the coil for which
+ * the status should be returned.
+ *
+ * @return true if set, false otherwise.
+ *
+ * @throws IndexOutOfBoundsException if the
+ * index is out of bounds
+ */
+ public boolean getCoilStatus(int index) throws IndexOutOfBoundsException {
+
+ if (index < 0) {
+ throw new IllegalArgumentException(index + " < 0");
+ }
+
+ if (index > coils.size()) {
+ throw new IndexOutOfBoundsException(index + " > " + coils.size());
+ }
+
+ return coils.getBit(index);
+ }
+
+ /**
+ * Sets the status of the given coil.
+ *
+ * @param index the index of the coil to be set.
+ * @param b true if to be set, false for reset.
+ */
+ public void setCoilStatus(int index, boolean b) {
+ if (index < 0) {
+ throw new IllegalArgumentException(index + " < 0");
+ }
+
+ if (index > coils.size()) {
+ throw new IndexOutOfBoundsException(index + " > " + coils.size());
+ }
+
+ coils.setBit(index, b);
+ }
+
+ public void writeData(DataOutput output) throws IOException {
+ byte result[] = getMessage();
+
+ output.write(result);
+ }
+
+ public void readData(DataInput input) throws IOException {
+ int count = input.readUnsignedByte();
+ byte[] data = new byte[count];
+
+ input.readFully(data, 0, count);
+ coils = BitVector.createBitVector(data);
+ setDataLength(count + 1);
+ }
+
+ public byte[] getMessage() {
+ int len = 1 + coils.byteSize();
+ byte result[] = new byte[len];
+
+ result[0] = (byte)coils.byteSize();
+ System.arraycopy(coils.getBytes(), 0, result, 1, coils.byteSize());
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterRequest.java
new file mode 100644
index 0000000..7709439
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read MEI Data request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCommEventCounterRequest extends ModbusRequest {
+
+ /**
+ * Constructs a new Report Slave ID request instance.
+ */
+ public ReadCommEventCounterRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_COMM_EVENT_COUNTER);
+
+ // There is no additional data in this request.
+ setDataLength(0);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadCommEventCounterResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- dummy function. There is no additional data
+ * to read.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ }
+
+ /**
+ * getMessage
+ * @return an empty array as there is no data for this request
+ */
+ public byte[] getMessage() {
+
+ return new byte[0];
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterResponse.java
new file mode 100644
index 0000000..c12a709
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventCounterResponse.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadCommEventCounterResponse.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCommEventCounterResponse extends ModbusResponse {
+
+ // Message fields.
+ private int status;
+ private int events;
+
+ /**
+ * Constructs a new ReportSlaveIDResponse instance.
+ */
+ public ReadCommEventCounterResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_COMM_EVENT_COUNTER);
+ setDataLength(4);
+ }
+
+ /**
+ * getStatus -- get the device's status.
+ *
+ * @return int
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * setStatus -- set the device's status.
+ *
+ * @param status int
+ */
+ public void setStatus(int status) {
+ if (status != 0 && status != 0xFFFF) {
+ throw new IllegalArgumentException("Illegal status value: " + status);
+ }
+
+ this.status = status;
+ }
+
+ /**
+ * getEvents -- get device's event counter.
+ * @return Event count
+ */
+ public int getEventCount() {
+ return events;
+ }
+
+ /**
+ * setEvents -- set the device's event counter.
+ * @param count Event count
+ */
+ public void setEventCount(int count) {
+ events = count;
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a header,
+ * such as for Modbus/TCP, it will have been read already.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ status = din.readUnsignedShort();
+ events = din.readUnsignedShort();
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Response as byte array
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)(status >> 8);
+ result[1] = (byte)(status & 0xFF);
+ result[2] = (byte)(events >> 8);
+ result[3] = (byte)(events & 0xFF);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogRequest.java
new file mode 100644
index 0000000..04e27ee
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read MEI Data request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCommEventLogRequest extends ModbusRequest {
+
+ /**
+ * Constructs a new Get Comm Event Log
+ * instance.
+ */
+ public ReadCommEventLogRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_COMM_EVENT_LOG);
+
+ // There is no additional data in this request.
+ setDataLength(0);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadCommEventLogResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- dummy function. There is no data with the request.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ }
+
+ /**
+ * getMessage
+ * @return an empty array as there is no data for this request
+ */
+ public byte[] getMessage() {
+
+ return new byte[0];
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogResponse.java
new file mode 100644
index 0000000..8911aa7
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadCommEventLogResponse.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadCommEventCounterResponse.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadCommEventLogResponse extends ModbusResponse {
+
+ // Message fields.
+ private int byteCount;
+ private int status;
+ private int eventCount;
+ private int messageCount;
+ private byte[] events;
+
+ /**
+ * Constructs a new ReadCommEventLogResponse instance.
+ */
+ public ReadCommEventLogResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_COMM_EVENT_LOG);
+ setDataLength(7);
+ }
+
+ /**
+ * getStatus -- get the device's status.
+ *
+ * @return int
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * setStatus -- set the device's status.
+ *
+ * @param status Status to set
+ */
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ /**
+ * getEvents -- get device's event counter.
+ * @return Number of events
+ */
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ /**
+ * setEventCount -- set the device's event counter.
+ * @param count Set the event count
+ */
+ public void setEventCount(int count) {
+ eventCount = count;
+ }
+
+ /**
+ * getMessageCount -- get device's message counter.
+ *
+ * @return Number of messages
+ */
+ public int getMessageCount() {
+ return messageCount;
+ }
+
+ /**
+ * setMessageCount -- set device's message counter.
+ * @param count Number of messages
+ */
+ public void setMessageCount(int count) {
+ messageCount = count;
+ }
+
+ /**
+ * getEvent -- get an event from the event log.
+ * @param index Index of the event
+ * @return Event ID
+ */
+ public int getEvent(int index) {
+ if (events == null || index < 0 || index >= events.length) {
+ throw new IndexOutOfBoundsException("index = " + index + ", limit = " + (events == null ? "null" : events.length));
+ }
+
+ return events[index] & 0xFF;
+ }
+
+ public byte[] getEvents() {
+ if (events == null) {
+ return null;
+ }
+
+ byte[] result = new byte[events.length];
+ System.arraycopy(events, 0, result, 0, events.length);
+
+ return result;
+ }
+
+ public void setEvents(byte[] events) {
+ if (events.length > 64) {
+ throw new IllegalArgumentException("events list too big (> 64 bytes)");
+ }
+
+ events = new byte[events.length];
+ if (events.length > 0) {
+ System.arraycopy(events, 0, events, 0, events.length);
+ }
+ }
+
+ public void setEvents(int count) {
+ if (count < 0 || count > 64) {
+ throw new IllegalArgumentException("invalid event list size (0 <= count <= 64)");
+ }
+
+ events = new byte[count];
+ }
+
+ /**
+ * setEvent -- store an event number in the event log
+ * @param index Event position
+ * @param event Event ID
+ */
+ public void setEvent(int index, int event) {
+ if (events == null || index < 0 || index >= events.length) {
+ throw new IndexOutOfBoundsException("index = " + index + ", limit = " + (events == null ? "null" : events.length));
+ }
+
+ events[index] = (byte)event;
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a header,
+ * such as for Modbus/TCP, it will have been read already.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ byteCount = din.readByte();
+ status = din.readUnsignedShort();
+ eventCount = din.readUnsignedShort();
+ messageCount = din.readUnsignedShort();
+
+ events = new byte[byteCount - 6];
+
+ if (events.length > 0) {
+ din.readFully(events, 0, events.length);
+ }
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Response as byte array
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[events.length + 7];
+
+ result[0] = (byte)(byteCount = events.length + 6);
+ result[1] = (byte)(status >> 8);
+ result[2] = (byte)(status & 0xFF);
+ result[3] = (byte)(eventCount >> 8);
+ result[4] = (byte)(eventCount & 0xFF);
+ result[5] = (byte)(messageCount >> 8);
+ result[6] = (byte)(messageCount & 0xFF);
+
+ System.arraycopy(events, 0, result, 7, events.length);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusRequest.java
new file mode 100644
index 0000000..a13c3f5
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read Exception Status request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadExceptionStatusRequest extends ModbusRequest {
+
+ /**
+ * Constructs a new Read Exception Status request
+ * instance.
+ */
+ public ReadExceptionStatusRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_EXCEPTION_STATUS);
+
+ // There is no additional data in this request.
+ setDataLength(0);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadExceptionStatusResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- dummy function. There is no data with the request.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ }
+
+ /**
+ * getMessage
+ * @return an empty array as there is no data for this request
+ */
+ public byte[] getMessage() {
+
+ return new byte[0];
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusResponse.java
new file mode 100644
index 0000000..7f26e66
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadExceptionStatusResponse.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadCommEventCounterResponse.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadExceptionStatusResponse extends ModbusResponse {
+
+ // Message fields.
+ private int status;
+
+ /**
+ * Constructs a new ReadExceptionStatusResponse instance.
+ */
+ public ReadExceptionStatusResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_EXCEPTION_STATUS);
+ setDataLength(1);
+ }
+
+ /**
+ * getStatus -- get the device's status.
+ *
+ * @return int
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * setStatus -- set the device's status.
+ *
+ * @param status Status to set
+ */
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a header,
+ * such as for Modbus/TCP, it will have been read already.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ status = din.readByte() & 0xFF;
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Response as byte array
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[1];
+
+ result[0] = (byte)(status & 0xFF);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueRequest.java
new file mode 100644
index 0000000..86885d7
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.Register;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read FIFO Queue request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadFIFOQueueRequest extends ModbusRequest {
+
+ private int reference;
+
+ /**
+ * Constructs a new Read FIFO Queue request instance.
+ */
+ public ReadFIFOQueueRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_FIFO_QUEUE);
+ setDataLength(2);
+ }
+
+ /**
+ * getReference -- get the queue register number.
+ *
+ * @return int
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference -- set the queue register number.
+ *
+ * @param ref Register
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadFIFOQueueResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadFIFOQueueResponse response;
+ InputRegister[] registers;
+
+ // Get the process image.
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ try {
+ // Get the FIFO queue location and read the count of available
+ // registers.
+ Register queue = procimg.getRegister(reference);
+ int count = queue.getValue();
+ if (count < 0 || count > 31) {
+ return createExceptionResponse(Modbus.ILLEGAL_VALUE_EXCEPTION);
+ }
+ registers = procimg.getRegisterRange(reference + 1, count);
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (ReadFIFOQueueResponse)getResponse();
+ response.setRegisters(registers);
+
+ return response;
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If cannot write
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- read the reference word.
+ * @throws IOException If cannot read
+ */
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ }
+
+ /**
+ * getMessage
+ * @return an empty array as there is no data for this request
+ */
+ public byte[] getMessage() {
+ byte results[] = new byte[2];
+
+ results[0] = (byte)(reference >> 8);
+ results[1] = (byte)(reference & 0xFF);
+
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueResponse.java
new file mode 100644
index 0000000..5163893
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFIFOQueueResponse.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a ReadFIFOQueueResponse.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadFIFOQueueResponse extends ModbusResponse {
+
+ // Message fields.
+ private int count;
+ private InputRegister registers[];
+
+ /**
+ * Constructs a new ReadFIFOQueueResponse instance.
+ */
+ public ReadFIFOQueueResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_FIFO_QUEUE);
+
+ count = 0;
+ registers = new InputRegister[0];
+
+ setDataLength(7);
+ }
+
+ /**
+ * getWordCount -- get the queue size.
+ *
+ * @return Word count int
+ */
+ synchronized public int getWordCount() {
+ return count;
+ }
+
+ /**
+ * setWordCount -- set the queue size.
+ *
+ * @param ref Register
+ */
+ public synchronized void setWordCount(int ref) {
+ if (ref < 0 || ref > 31) {
+ throw new IllegalArgumentException();
+ }
+ count = ref;
+ }
+
+ synchronized public int[] getRegisters() {
+ int values[] = new int[count];
+
+ for (int i = 0; i < count; i++) {
+ values[i] = getRegister(i);
+ }
+
+ return values;
+ }
+
+ /**
+ * setRegisters -- set the device's status.
+ *
+ * @param regs Array of registers
+ */
+ public synchronized void setRegisters(InputRegister[] regs) {
+ if (regs == null) {
+ registers = null;
+ count = 0;
+ return;
+ }
+
+ registers = Arrays.copyOf(regs, regs.length);
+ if (regs.length > 31) {
+ throw new IllegalArgumentException();
+ }
+
+ count = regs.length;
+ }
+
+ public int getRegister(int index) {
+ return registers[index].getValue();
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a header,
+ * such as for Modbus/TCP, it will have been read already.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+
+ /*
+ * Read and discard the byte count. There's no way to indicate
+ * the packet was inconsistent, other than throwing an I/O
+ * exception for an invalid packet format ...
+ */
+ din.readShort();
+
+ // The first register is the number of registers which
+ // follow. Save that as count, not as a register.
+ count = din.readUnsignedShort();
+ registers = new InputRegister[count];
+
+ for (int i = 0; i < count; i++) {
+ registers[i] = new SimpleInputRegister(din.readShort());
+ }
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Byte array of message
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[count * 2 + 4];
+
+ int len = count * 2 + 2;
+ result[0] = (byte)(len >> 8);
+ result[1] = (byte)(len & 0xFF);
+ result[2] = (byte)(count >> 8);
+ result[3] = (byte)(count & 0xFF);
+
+ for (int i = 0; i < count; i++) {
+ byte value[] = registers[i].toBytes();
+ result[i * 2 + 4] = value[0];
+ result[i * 2 + 5] = value[1];
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordRequest.java
new file mode 100644
index 0000000..a0c646c
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordRequest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.msg.ReadFileRecordResponse.RecordResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.*;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read File Record request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadFileRecordRequest extends ModbusRequest {
+ private RecordRequest[] records;
+
+ /**
+ * Constructs a new Read File Record request instance.
+ */
+ public ReadFileRecordRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_FILE_RECORD);
+
+ // Request size byte is all that is required.
+ setDataLength(1);
+ }
+
+ /**
+ * getRequestSize -- return the total request size. This is useful for
+ * determining if a new record can be added.
+ *
+ * @return size in bytes of response.
+ */
+ public int getRequestSize() {
+ if (records == null) {
+ return 1;
+ }
+
+ int size = 1;
+ for (RecordRequest record : records) {
+ size += record.getRequestSize();
+ }
+
+ return size;
+ }
+
+ /**
+ * getRequestCount
+ * @return the number of record requests in this message
+ */
+ public int getRequestCount() {
+ if (records == null) {
+ return 0;
+ }
+
+ return records.length;
+ }
+
+ /**
+ * getRecord
+ * @param index Reference
+ * @return the record request indicated by the reference
+ */
+ public RecordRequest getRecord(int index) {
+ return records[index];
+ }
+
+ /**
+ * addRequest -- add a new record request.
+ * @param request Record request to add
+ */
+ public void addRequest(RecordRequest request) {
+ if (request.getRequestSize() + getRequestSize() > 248) {
+ throw new IllegalArgumentException();
+ }
+
+ if (records == null) {
+ records = new RecordRequest[1];
+ }
+ else {
+ RecordRequest old[] = records;
+ records = new RecordRequest[old.length + 1];
+
+ System.arraycopy(old, 0, records, 0, old.length);
+ }
+ records[records.length - 1] = request;
+
+ setDataLength(getRequestSize());
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadFileRecordResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadFileRecordResponse response = (ReadFileRecordResponse)getResponse();
+
+ // Get the process image.
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ // There is a list of requests to be resolved.
+ try {
+ for (int i = 0; i < getRequestCount(); i++) {
+ RecordRequest recordRequest = getRecord(i);
+ if (recordRequest.getFileNumber() < 0 || recordRequest.getFileNumber() >= procimg.getFileCount()) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ File file = procimg.getFileByNumber(recordRequest.getFileNumber());
+
+ if (recordRequest.getRecordNumber() < 0 || recordRequest.getRecordNumber() >= file.getRecordCount()) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ Record record = file.getRecord(recordRequest.getRecordNumber());
+ int registers = recordRequest.getWordCount();
+ if (record == null && registers != 0) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ short data[] = new short[registers];
+ for (int j = 0; j < registers; j++) {
+ Register register = record.getRegister(j);
+ if (register == null) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ data[j] = register.toShort();
+ }
+ RecordResponse recordResponse = new RecordResponse(data);
+ response.addResponse(recordResponse);
+ }
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ return response;
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- read all the data for this request.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ int byteCount = din.readUnsignedByte();
+
+ int recordCount = byteCount / 7;
+ records = new RecordRequest[recordCount];
+
+ for (int i = 0; i < recordCount; i++) {
+ if (din.readByte() != 6) {
+ throw new IOException();
+ }
+
+ int file = din.readUnsignedShort();
+ int record = din.readUnsignedShort();
+ if (record < 0 || record >= 10000) {
+ throw new IOException();
+ }
+
+ int count = din.readUnsignedShort();
+
+ records[i] = new RecordRequest(file, record, count);
+ }
+ }
+
+ /**
+ * getMessage
+ * @return the PDU message
+ */
+ public byte[] getMessage() {
+ byte request[] = new byte[1 + 7 * records.length];
+
+ int offset = 0;
+ request[offset++] = (byte)(request.length - 1);
+
+ for (RecordRequest record : records) {
+ record.getRequest(request, offset);
+ offset += 7;
+ }
+ return request;
+ }
+
+ public static class RecordRequest {
+ private int fileNumber;
+ private int recordNumber;
+ private int wordCount;
+
+ public RecordRequest(int file, int record, int count) {
+ fileNumber = file;
+ recordNumber = record;
+ wordCount = count;
+ }
+
+ public int getFileNumber() {
+ return fileNumber;
+ }
+
+ public int getRecordNumber() {
+ return recordNumber;
+ }
+
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ /**
+ * getRequestSize
+ * @return the size of the response in bytes
+ */
+ public int getRequestSize() {
+ return 7 + wordCount * 2;
+ }
+
+ public void getRequest(byte[] request, int offset) {
+ request[offset] = 6;
+ request[offset + 1] = (byte)(fileNumber >> 8);
+ request[offset + 2] = (byte)(fileNumber & 0xFF);
+ request[offset + 3] = (byte)(recordNumber >> 8);
+ request[offset + 4] = (byte)(recordNumber & 0xFF);
+ request[offset + 5] = (byte)(wordCount >> 8);
+ request[offset + 6] = (byte)(wordCount & 0xFF);
+ }
+
+ public byte[] getRequest() {
+ byte[] request = new byte[7];
+
+ getRequest(request, 0);
+
+ return request;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordResponse.java
new file mode 100644
index 0000000..9dc9054
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadFileRecordResponse.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadFileRecordResponse.
+ *
+ * @author Julie (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadFileRecordResponse extends ModbusResponse {
+
+ private RecordResponse[] records = null;
+
+ /**
+ * Constructs a new ReadFileRecordResponse instance.
+ */
+ public ReadFileRecordResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_FILE_RECORD);
+ }
+
+ /**
+ * Returns the number of bytes needed for the response.
+ *
+ * The response is 1 byte for the total response size, plus
+ * the sum of the sizes of all the records in the response.
+ *
+ * @return the number of bytes in the response.
+ */
+ public int getByteCount() {
+ if (records == null) {
+ return 1;
+ }
+
+ int size = 1;
+ for (RecordResponse record : records) {
+ size += record.getResponseSize();
+ }
+
+ return size;
+ }
+
+ /**
+ * getRecordCount -- return the number of records in the response.
+ *
+ * @return count of records in response.
+ */
+ public int getRecordCount() {
+ if (records == null) {
+ return 0;
+ }
+
+ return records.length;
+ }
+
+ /**
+ * getRecord
+ * @param index Record to get
+ * @return the record response indicated by the reference
+ */
+ public RecordResponse getRecord(int index) {
+ return records[index];
+ }
+
+ /**
+ * addResponse -- add a new record response.
+ * @param response Record response to add
+ */
+ public void addResponse(RecordResponse response) {
+ if (records == null) {
+ records = new RecordResponse[1];
+ }
+ else {
+ RecordResponse old[] = records;
+ records = new RecordResponse[old.length + 1];
+
+ System.arraycopy(old, 0, records, 0, old.length);
+ }
+ records[records.length - 1] = response;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(getByteCount() - 1);
+
+ if (records == null) {
+ return;
+ }
+
+ for (RecordResponse record : records) {
+ dout.write(record.getResponse());
+ }
+ }
+
+ public void readData(DataInput din) throws IOException {
+ int byteCount = (din.readUnsignedByte() & 0xFF);
+
+ int remainder = byteCount;
+ while (remainder > 0) {
+ int length = din.readUnsignedByte();
+ remainder--;
+
+ int function = din.readByte();
+ remainder--;
+
+ if (function != 6 || (length - 1) > remainder) {
+ throw new IOException("Invalid response format");
+ }
+ short[] data = new short[(length - 1) / 2];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = din.readShort();
+ remainder -= 2;
+ }
+ RecordResponse response = new RecordResponse(data);
+ addResponse(response);
+ }
+ setDataLength(byteCount + 1);
+ }
+
+ public byte[] getMessage() {
+ byte result[];
+
+ result = new byte[getByteCount()];
+
+ int offset = 0;
+ result[offset++] = (byte)(result.length - 1);
+
+ for (RecordResponse record : records) {
+ record.getResponse(result, offset);
+ offset += record.getWordCount() * 2;
+ }
+ return result;
+ }
+
+ public static class RecordResponse {
+ private int wordCount;
+ private byte[] data;
+
+ public RecordResponse(short data[]) {
+ wordCount = data.length;
+ this.data = new byte[wordCount * 2];
+
+ int offset = 0;
+ for (int i = 0; i < wordCount; i++) {
+ this.data[offset++] = (byte)(data[i] >> 8);
+ this.data[offset++] = (byte)(data[i] & 0xFF);
+ }
+ }
+
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ public SimpleRegister getRegister(int register) {
+ if (register < 0 || register >= wordCount) {
+ throw new IndexOutOfBoundsException("0 <= " + register + " < " + wordCount);
+ }
+ byte b1 = data[register * 2];
+ byte b2 = data[register * 2 + 1];
+
+ return new SimpleRegister(b1, b2);
+ }
+
+ /**
+ * getResponseSize -- return the size of the response in bytes.
+ *
+ * The response is a byte count, a function code, then wordCount
+ * words (2 bytes).
+ * @return the size of the response in bytes
+ */
+ public int getResponseSize() {
+ return 2 + (wordCount * 2);
+ }
+
+ /**
+ * getResponse - return the response data for this record
+ *
+ * The response data is the byte size of the response, minus this
+ * byte, the function code (6), then the raw byte data for the
+ * registers (wordCount * 2 bytes).
+ *
+ * @param request Request message
+ * @param offset Offset into buffer
+ */
+ public void getResponse(byte[] request, int offset) {
+ request[offset] = (byte)(1 + (wordCount * 2));
+ request[offset + 1] = 6;
+ System.arraycopy(data, 0, request, offset + 2, data.length);
+ }
+
+ public byte[] getResponse() {
+ byte[] request = new byte[getResponseSize()];
+ getResponse(request, 0);
+ return request;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesRequest.java
new file mode 100644
index 0000000..8258e54
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesRequest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.DigitalIn;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadInputDiscretesRequest. The implementation
+ * directly correlates with the class 1 function read input discretes (FC
+ * 2). It encapsulates the corresponding request message.
+ *
+ * Input Discretes are understood as bits that cannot be manipulated (i.e. set
+ * or unset).
+ *
+ * @author Dieter Wimberger
+ * @author jfhaugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadInputDiscretesRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private int bitCount;
+
+ /**
+ * Constructs a new ReadInputDiscretesRequest instance.
+ */
+ public ReadInputDiscretesRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_DISCRETES);
+
+ // Two bytes for count, two bytes for offset.
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new ReadInputDiscretesRequest instance with a given
+ * reference and count of input discretes (i.e. bits) to be read.
+ *
+ *
+ * @param ref the reference number of the register to read from.
+ * @param count the number of bits to be read.
+ */
+ public ReadInputDiscretesRequest(int ref, int count) {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_DISCRETES);
+ // 4 bytes (unit id and function code is excluded)
+ setDataLength(4);
+ setReference(ref);
+ setBitCount(count);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadInputDiscretesResponse(getBitCount()));
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadInputDiscretesResponse response;
+ DigitalIn[] dins;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get input discretes range
+ try {
+ dins = procimg.getDigitalInRange(getReference(), getBitCount());
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (ReadInputDiscretesResponse)getResponse();
+
+ // Populate the discrete values from the process image.
+ for (int i = 0; i < dins.length; i++) {
+ response.setDiscreteStatus(i, dins[i].isSet());
+ }
+
+ return response;
+ }
+
+ /**
+ * Returns the reference of the discrete to to start reading from with
+ * this ReadInputDiscretesRequest.
+ *
+ * @return the reference of the discrete to start reading from as
+ * int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to start reading from with this
+ * ReadInputDiscretesRequest.
+ *
+ *
+ * @param ref the reference of the register to start reading from.
+ */
+ public void setReference(int ref) {
+ if (ref < 0 || bitCount + ref >= 65536) {
+ throw new IllegalArgumentException();
+ }
+
+ reference = ref;
+ }
+
+ /**
+ * Returns the number of bits (i.e. input discretes) to be read with this
+ * ReadInputDiscretesRequest.
+ *
+ *
+ * @return the number of bits to be read.
+ */
+ public int getBitCount() {
+ return bitCount;
+ }
+
+ /**
+ * Sets the number of bits (i.e. input discretes) to be read with this
+ * ReadInputDiscretesRequest.
+ *
+ * @param count the number of bits to be read.
+ */
+ public void setBitCount(int count) {
+ if (count < 0 || count > 2000 || count + reference >= 65536) {
+ throw new IllegalArgumentException();
+ }
+
+ bitCount = count;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+ dout.writeShort(bitCount);
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ bitCount = din.readUnsignedShort();
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)((reference & 0xff));
+ result[2] = (byte)((bitCount >> 8) & 0xff);
+ result[3] = (byte)((bitCount & 0xff));
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesResponse.java
new file mode 100644
index 0000000..c6cc891
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputDiscretesResponse.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.util.BitVector;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadInputDiscretesResponse.
+ * The implementation directly correlates with the class 1
+ * function read input discretes (FC 2). It encapsulates
+ * the corresponding response message.
+ *
+ * Input Discretes are understood as bits that cannot be
+ * manipulated (i.e. set or unset).
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadInputDiscretesResponse
+ extends ModbusResponse {
+
+ //instance attributes
+ private int bitCount;
+ private BitVector discretes;
+
+ /**
+ * Constructs a new ReadInputDiscretesResponse
+ * instance.
+ */
+ public ReadInputDiscretesResponse() {
+ super();
+ setFunctionCode(Modbus.READ_INPUT_DISCRETES);
+ }
+
+ /**
+ * Constructs a new ReadInputDiscretesResponse
+ * instance with a given count of input discretes
+ * (i.e. bits).
+ *
+ * @param count the number of bits to be read.
+ */
+ public ReadInputDiscretesResponse(int count) {
+ super();
+ setFunctionCode(Modbus.READ_INPUT_DISCRETES);
+ setBitCount(count);
+ }
+
+ /**
+ * Returns the number of bits (i.e. input discretes)
+ * read with the request.
+ *
+ * @return the number of bits that have been read.
+ */
+ public int getBitCount() {
+ return bitCount;
+ }
+
+ /**
+ * Sets the number of bits in this response.
+ *
+ * @param count the number of response bits as int.
+ */
+ public void setBitCount(int count) {
+ bitCount = count;
+ discretes = new BitVector(count);
+ //set correct length, without counting unitid and fc
+ setDataLength(discretes.byteSize() + 1);
+ }
+
+ /**
+ * Returns the BitVector that stores
+ * the collection of bits that have been read.
+ *
+ *
+ * @return the BitVector holding the
+ * bits that have been read.
+ */
+ public BitVector getDiscretes() {
+ return discretes;
+ }
+
+ /**
+ * Convenience method that returns the state
+ * of the bit at the given index.
+ *
+ *
+ * @param index the index of the input discrete
+ * for which the status should be returned.
+ *
+ * @return true if set, false otherwise.
+ *
+ * @throws IndexOutOfBoundsException if the
+ * index is out of bounds
+ */
+ public boolean getDiscreteStatus(int index) throws IndexOutOfBoundsException {
+
+ return discretes.getBit(index);
+ }
+
+ /**
+ * Sets the status of the given input discrete.
+ *
+ * @param index the index of the input discrete to be set.
+ * @param b true if to be set, false if to be reset.
+ *
+ * @throws IndexOutOfBoundsException if the given index exceeds bounds.
+ */
+ public void setDiscreteStatus(int index, boolean b) throws IndexOutOfBoundsException {
+ discretes.setBit(index, b);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(discretes.byteSize());
+ dout.write(discretes.getBytes(), 0, discretes.byteSize());
+ }
+
+ public void readData(DataInput din) throws IOException {
+
+ int count = din.readUnsignedByte();
+ byte[] data = new byte[count];
+ for (int k = 0; k < count; k++) {
+ data[k] = din.readByte();
+ }
+
+ //decode bytes into bitvector
+ discretes = BitVector.createBitVector(data);
+ if (discretes != null) {
+ bitCount = discretes.size();
+ }
+
+ //update data length
+ setDataLength(count + 1);
+ }
+
+ public byte[] getMessage() {
+ byte result[];
+ int len = 1 + discretes.byteSize();
+
+ result = new byte[len];
+ result[0] = (byte)discretes.byteSize();
+ System.arraycopy(discretes.getBytes(), 0, result, 1, discretes.byteSize());
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersRequest.java
new file mode 100644
index 0000000..ea13c81
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersRequest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadInputRegistersRequest. The implementation
+ * directly correlates with the class 0 function read multiple registers (FC
+ * 4). It encapsulates the corresponding request message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadInputRegistersRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private int wordCount;
+
+ /**
+ * Constructs a new ReadInputRegistersRequest instance.
+ */
+ public ReadInputRegistersRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_REGISTERS);
+ // 4 bytes (unit id and function code is excluded)
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new ReadInputRegistersRequest instance with a given
+ * reference and count of words to be read.
+ *
+ *
+ * @param ref the reference number of the register to read from.
+ * @param count the number of words to be read.
+ */
+ public ReadInputRegistersRequest(int ref, int count) {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_REGISTERS);
+ // 4 bytes (unit id and function code is excluded)
+ setDataLength(4);
+
+ setReference(ref);
+ setWordCount(count);
+ }
+
+ public ReadInputRegistersResponse getResponse() {
+ ReadInputRegistersResponse response = (ReadInputRegistersResponse)updateResponseWithHeader(new ReadInputRegistersResponse());
+ response.setWordCount(getWordCount());
+ return response;
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadInputRegistersResponse response;
+ InputRegister[] inpregs;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get input registers range
+ try {
+ inpregs = procimg.getInputRegisterRange(getReference(), getWordCount());
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = getResponse();
+ response.setRegisters(inpregs);
+
+ return response;
+ }
+
+ /**
+ * Returns the reference of the register to to start reading from with this
+ * ReadInputRegistersRequest.
+ *
+ *
+ * @return the reference of the register to start reading from as
+ * int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to start reading from with this
+ * ReadInputRegistersRequest.
+ *
+ *
+ * @param ref the reference of the register to start reading from.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the number of words to be read with this
+ * ReadInputRegistersRequest.
+ *
+ *
+ * @return the number of words to be read as int.
+ */
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ /**
+ * Sets the number of words to be read with this
+ * ReadInputRegistersRequest.
+ *
+ *
+ * @param count the number of words to be read.
+ */
+ public void setWordCount(int count) {
+ wordCount = count;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+ dout.writeShort(wordCount);
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ wordCount = din.readUnsignedShort();
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((wordCount >> 8) & 0xff);
+ result[3] = (byte)(wordCount & 0xff);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersResponse.java
new file mode 100644
index 0000000..98549ec
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadInputRegistersResponse.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.SimpleInputRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a ReadInputRegistersRequest. The implementation
+ * directly correlates with the class 0 function read multiple registers (FC
+ * 4). It encapsulates the corresponding response message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadInputRegistersResponse extends ModbusResponse {
+
+ // instance attributes
+ private int byteCount;
+ private InputRegister[] registers;
+
+ /**
+ * Constructs a new ReadInputRegistersResponse instance.
+ */
+ public ReadInputRegistersResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_REGISTERS);
+ }
+
+ /**
+ * Constructs a new ReadInputRegistersResponse instance.
+ *
+ * @param registers the InputRegister[] holding response input registers.
+ */
+ public ReadInputRegistersResponse(InputRegister[] registers) {
+ super();
+
+ setFunctionCode(Modbus.READ_INPUT_REGISTERS);
+ setDataLength(registers == null ? 0 : (registers.length * 2 + 1));
+
+ this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
+ byteCount = registers == null ? 0 : (registers.length * 2);
+ }
+
+ /**
+ * Returns the number of bytes that have been read.
+ *
+ * @return the number of bytes that have been read as int.
+ */
+ public int getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * Returns the number of words that have been read. The returned value
+ * should be half as much as the byte count of the response.
+ *
+ * @return the number of words that have been read as int.
+ */
+ public int getWordCount() {
+ return byteCount / 2;
+ }
+
+ /**
+ * Set the number of words to be written.
+ * @param count Number of words in response
+ */
+ public void setWordCount(int count) {
+ byteCount = count * 2;
+ }
+
+ /**
+ * Returns the InputRegister at the given position (relative to the
+ * reference used in the request).
+ *
+ * @param index the relative index of the InputRegister.
+ *
+ * @return the register as InputRegister.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public InputRegister getRegister(int index) throws IndexOutOfBoundsException {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(index + " < 0");
+ }
+
+ if (index >= getWordCount()) {
+ throw new IndexOutOfBoundsException(index + " >= " + getWordCount());
+ }
+
+ return registers[index];
+ }
+
+ /**
+ * Returns the value of the register at the given position (relative to the
+ * reference used in the request) interpreted as usigned short.
+ *
+ * @param index the relative index of the register for which the value should
+ * be retrieved.
+ *
+ * @return the unsigned short value as an int.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int getRegisterValue(int index) throws IndexOutOfBoundsException {
+ return getRegister(index).toUnsignedShort();
+ }
+
+ /**
+ * Returns a reference to the array of input registers read.
+ *
+ * @return a InputRegister[] instance.
+ */
+ public synchronized InputRegister[] getRegisters() {
+ InputRegister[] dest = new InputRegister[registers.length];
+ System.arraycopy(registers, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Sets the entire block of registers for this response
+ * @param registers Array of registers
+ */
+ public void setRegisters(InputRegister[] registers) {
+ setDataLength(registers == null ? 0 : (registers.length * 2 + 1));
+ this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
+ byteCount = registers == null ? 0 : (registers.length * 2);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(byteCount);
+
+ for (int k = 0; k < getWordCount(); k++) {
+ dout.write(registers[k].toBytes());
+ }
+ }
+
+ public void readData(DataInput din) throws IOException {
+ byteCount = din.readUnsignedByte();
+
+ InputRegister[] registers = new InputRegister[getWordCount()];
+ for (int k = 0; k < getWordCount(); k++) {
+ registers[k] = new SimpleInputRegister(din.readByte(), din.readByte());
+ }
+ this.registers = registers;
+
+ setDataLength(byteCount);
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[registers.length * 2 + 1];
+ result[0] = (byte)(registers.length * 2);
+
+ for (int i = 0; i < registers.length; i++) {
+ byte value[] = registers[i].toBytes();
+
+ result[1 + i * 2] = value[0];
+ result[2 + i * 2] = value[1];
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIRequest.java
new file mode 100644
index 0000000..a40ea14
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIRequest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read MEI Data request.
+ *
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadMEIRequest extends ModbusRequest {
+
+ // instance attributes
+ private int subCode;
+ private int fieldLevel;
+ private int fieldId;
+
+ /**
+ * Constructs a new Read MEI Data request instance.
+ */
+ public ReadMEIRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_MEI);
+ subCode = 0x0E;
+
+ // 3 bytes (unit id and function code is excluded)
+ setDataLength(3);
+ }
+
+ /**
+ * Constructs a new Read MEI Data request instance with a given
+ * reference and count of coils (i.e. bits) to be read.
+ *
+ *
+ * @param level the reference number of the register to read from.
+ * @param id the number of bits to be read.
+ */
+ public ReadMEIRequest(int level, int id) {
+ super();
+
+ setFunctionCode(Modbus.READ_MEI);
+ subCode = 0x0E;
+
+ // 3 bytes (unit id and function code is excluded)
+ setDataLength(3);
+ setLevel(level);
+ setFieldId(id);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+
+ // Any other sub-function is an error.
+ if (getSubCode() != 0x0E) {
+ IllegalFunctionExceptionResponse error = new IllegalFunctionExceptionResponse();
+ return updateResponseWithHeader(error);
+ }
+ return updateResponseWithHeader(new ReadMEIResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * Gets the MEI subcode associated with this request.
+ * @return The MEI sub code
+ */
+ public int getSubCode() {
+ return subCode;
+ }
+
+ /**
+ * Returns the reference of the register to to start reading from with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @return the reference of the register to start reading from as
+ * int.
+ */
+ public int getLevel() {
+ return fieldLevel;
+ }
+
+ /**
+ * Sets the reference of the register to start reading from with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @param level the reference of the register to start reading from.
+ */
+ public void setLevel(int level) {
+ fieldLevel = level;
+ }
+
+ /**
+ * Returns the number of bits (i.e. coils) to be read with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @return the number of bits to be read.
+ */
+ public int getFieldId() {
+ return fieldId;
+ }
+
+ /**
+ * Sets the number of bits (i.e. coils) to be read with this
+ * ReadCoilsRequest.
+ *
+ *
+ * @param id the number of bits to be read.
+ */
+ public void setFieldId(int id) {
+ fieldId = id;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ byte results[] = new byte[3];
+
+ results[0] = (byte)subCode;
+ results[1] = (byte)fieldLevel;
+ results[2] = (byte)fieldId;
+
+ dout.write(results);
+ }
+
+ public void readData(DataInput din) throws IOException {
+ subCode = din.readUnsignedByte();
+
+ if (subCode != 0xE) {
+ try {
+ while (din.readByte() >= 0) {
+ }
+ }
+ catch (EOFException x) {
+ // do nothing.
+ }
+ return;
+ }
+ fieldLevel = din.readUnsignedByte();
+ fieldId = din.readUnsignedByte();
+ }
+
+ public byte[] getMessage() {
+ byte results[] = new byte[3];
+
+ results[0] = (byte)subCode;
+ results[1] = (byte)fieldLevel;
+ results[2] = (byte)fieldId;
+
+ return results;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIResponse.java
new file mode 100644
index 0000000..99e9556
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMEIResponse.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadMEIResponse.
+ *
+ * Derived from similar class for Read Coils response.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadMEIResponse extends ModbusResponse {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReadMEIResponse.class);
+
+ //instance attributes
+ private int fieldLevel = 0;
+ private int conformity = 1;
+ private int fieldCount = 0;
+ private String fields[] = new String[64];
+ private int fieldIds[] = new int[64];
+ private boolean moreFollows = false;
+ private int nextFieldId;
+
+ /**
+ * Constructs a new ReadMEIResponse
+ * instance.
+ */
+ public ReadMEIResponse() {
+ super();
+ setFunctionCode(Modbus.READ_MEI);
+ }
+
+ /**
+ * Returns the number of fields
+ * read with the request.
+ *
+ *
+ * @return the number of fields that have been read.
+ */
+ public int getFieldCount() {
+ if (fields == null) {
+ return 0;
+ }
+ else {
+ return fields.length;
+ }
+ }
+
+ /**
+ * Returns the array of strings that were read
+ * @return Array of the fields read
+ */
+ public synchronized String[] getFields() {
+ String[] dest = new String[fields.length];
+ System.arraycopy(fields, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Convenience method that returns the field
+ * at the requested index
+ *
+ *
+ * @param index the index of the field which
+ * should be returned.
+ *
+ * @return requested field
+ *
+ * @throws IndexOutOfBoundsException if the
+ * index is out of bounds
+ */
+ public String getField(int index) throws IndexOutOfBoundsException {
+ return fields[index];
+ }
+
+ /**
+ * Convenience method that returns the field
+ * ID at the given index.
+ *
+ *
+ * @param index the index of the field for which
+ * the ID should be returned.
+ *
+ * @return field ID
+ *
+ * @throws IndexOutOfBoundsException if the
+ * index is out of bounds
+ */
+ public int getFieldId(int index) throws IndexOutOfBoundsException {
+ return fieldIds[index];
+ }
+
+ public void setFieldLevel(int level) {
+ fieldLevel = level;
+ }
+
+ public void addField(int id, String text) {
+ fieldIds[fieldCount] = id;
+ fields[fieldCount] = text;
+ fieldCount++;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ int byteCount;
+
+ int subCode = din.readUnsignedByte();
+ if (subCode != 0xE) {
+ throw new IOException("Invalid sub code");
+ }
+
+ fieldLevel = din.readUnsignedByte();
+ conformity = din.readUnsignedByte();
+ moreFollows = din.readUnsignedByte() == 0xFF;
+ nextFieldId = din.readUnsignedByte();
+
+ fieldCount = din.readUnsignedByte();
+
+ byteCount = 6;
+
+ if (fieldCount > 0) {
+ fields = new String[fieldCount];
+ fieldIds = new int[fieldCount];
+
+ for (int i = 0; i < fieldCount; i++) {
+ fieldIds[i] = din.readUnsignedByte();
+ int len = din.readUnsignedByte();
+ byte data[] = new byte[len];
+ din.readFully(data);
+ fields[i] = new String(data, "UTF-8");
+
+ byteCount += 2 + len;
+ }
+ setDataLength(byteCount);
+ }
+ else {
+ setDataLength(byteCount);
+ }
+ }
+
+ public byte[] getMessage() {
+ int size = 6;
+
+ for (int i = 0; i < fieldCount; i++) {
+ // Add the field ID
+ size++;
+
+ // Add the string length byte and the
+ // actual string length.
+ size++;
+ size += fields[i].length();
+ }
+
+ byte result[] = new byte[size];
+ int offset = 0;
+
+ result[offset++] = 0x0E;
+ result[offset++] = (byte)fieldLevel;
+ result[offset++] = (byte)conformity;
+ result[offset++] = (byte)(moreFollows ? 0xFF : 0);
+ result[offset++] = (byte)nextFieldId;
+ result[offset++] = (byte)fieldCount;
+
+ for (int i = 0; i < fieldCount; i++) {
+ result[offset++] = (byte)fieldIds[i];
+ result[offset++] = (byte)fields[i].length();
+ try {
+ System.arraycopy(fields[i].getBytes("US-ASCII"), 0, result, offset, fields[i].length());
+ }
+ catch (Exception e) {
+ logger.debug("Problem converting bytes to string - {}", e.getMessage());
+ }
+ offset += fields[i].length();
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersRequest.java
new file mode 100644
index 0000000..a34e3d0
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersRequest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.Register;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadMultipleRegistersRequest. The
+ * implementation directly correlates with the class 0 function read multiple
+ * registers (FC 3). It encapsulates the corresponding request message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadMultipleRegistersRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private int wordCount;
+
+ /**
+ * Constructs a new ReadMultipleRegistersRequest instance.
+ */
+ public ReadMultipleRegistersRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new ReadMultipleRegistersRequest instance with a
+ * given reference and count of words to be read. This message reads
+ * from holding (r/w) registers.
+ *
+ * @param ref the reference number of the register to read from.
+ * @param count the number of words to be read.
+ *
+ * @see ReadInputRegistersRequest
+ */
+ public ReadMultipleRegistersRequest(int ref, int count) {
+ super();
+
+ setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
+ setDataLength(4);
+
+ setReference(ref);
+ setWordCount(count);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadMultipleRegistersResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadMultipleRegistersResponse response;
+ Register[] regs;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get input registers range
+ try {
+ regs = procimg.getRegisterRange(getReference(), getWordCount());
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (ReadMultipleRegistersResponse)getResponse();
+ response.setRegisters(regs);
+
+ return response;
+ }
+
+ /**
+ * Returns the reference of the register to to start reading from with this
+ * ReadMultipleRegistersRequest.
+ *
+ *
+ * @return the reference of the register to start reading from as
+ * int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to start reading from with this
+ * ReadMultipleRegistersRequest.
+ *
+ *
+ * @param ref the reference of the register to start reading from.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the number of words to be read with this
+ * ReadMultipleRegistersRequest.
+ *
+ *
+ * @return the number of words to be read as int.
+ */
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ /**
+ * Sets the number of words to be read with this
+ * ReadMultipleRegistersRequest.
+ *
+ *
+ * @param count the number of words to be read.
+ */
+ public void setWordCount(int count) {
+ wordCount = count;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+ dout.writeShort(wordCount);
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ wordCount = din.readUnsignedShort();
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((wordCount >> 8) & 0xff);
+ result[3] = (byte)(wordCount & 0xff);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersResponse.java
new file mode 100644
index 0000000..a84ebae
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadMultipleRegistersResponse.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.Register;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a ReadMultipleRegistersResponse. The
+ * implementation directly correlates with the class 0 function read multiple
+ * registers (FC 3). It encapsulates the corresponding response message.
+ *
+ * @author Dieter Wimberger
+ * @author Julie (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadMultipleRegistersResponse extends ModbusResponse {
+
+ // instance attributes
+ private int byteCount;
+ private Register[] registers;
+
+ /**
+ * Constructs a new ReadMultipleRegistersResponse instance.
+ */
+ public ReadMultipleRegistersResponse() {
+ super();
+ setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
+ }
+
+ /**
+ * Constructs a new ReadInputRegistersResponse instance.
+ *
+ * @param registers the Register[] holding response registers.
+ */
+ public ReadMultipleRegistersResponse(Register[] registers) {
+ super();
+
+ setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
+ setDataLength(registers == null ? 0 : (registers.length * 2 + 1));
+
+ this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
+ byteCount = registers == null ? 0 : (registers.length * 2);
+ }
+
+ /**
+ * Returns the number of bytes that have been read.
+ *
+ * @return the number of bytes that have been read as int.
+ */
+ public int getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * Returns the number of words that have been read. The returned value
+ * should be half of the the byte count of this
+ * ReadMultipleRegistersResponse.
+ *
+ * @return the number of words that have been read as int.
+ */
+ public int getWordCount() {
+ return byteCount / 2;
+ }
+
+ /**
+ * Returns the Register at the given position (relative to the
+ * reference used in the request).
+ *
+ * @param index the relative index of the Register.
+ *
+ * @return the register as Register.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public Register getRegister(int index) {
+ if (registers == null) {
+ throw new IndexOutOfBoundsException("No registers defined!");
+ }
+
+ if (index < 0) {
+ throw new IndexOutOfBoundsException("Negative index: " + index);
+ }
+
+ if (index >= getWordCount()) {
+ throw new IndexOutOfBoundsException(index + " > " + getWordCount());
+ }
+
+ return registers[index];
+ }
+
+ /**
+ * Returns the value of the register at the given position (relative to the
+ * reference used in the request) interpreted as unsigned short.
+ *
+ * @param index the relative index of the register for which the value should
+ * be retrieved.
+ *
+ * @return the value as int.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int getRegisterValue(int index) throws IndexOutOfBoundsException {
+ return getRegister(index).toUnsignedShort();
+ }
+
+ /**
+ * Returns the reference to the array of registers read.
+ *
+ * @return a Register[] instance.
+ */
+ public synchronized Register[] getRegisters() {
+ Register[] dest = new Register[registers.length];
+ System.arraycopy(registers, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Sets the entire block of registers for this response
+ * @param registers Array of registers to use
+ */
+ public void setRegisters(Register[] registers) {
+ byteCount = registers == null ? 0 : registers.length * 2;
+ this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
+ setDataLength(byteCount + 1);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(byteCount);
+
+ for (int k = 0; k < getWordCount(); k++) {
+ dout.write(registers[k].toBytes());
+ }
+ }
+
+ public void readData(DataInput din) throws IOException {
+ byteCount = din.readUnsignedByte();
+
+ registers = new Register[getWordCount()];
+
+ for (int k = 0; k < getWordCount(); k++) {
+ registers[k] = new SimpleRegister(din.readByte(), din.readByte());
+ }
+
+ setDataLength(byteCount + 1);
+ }
+
+ public byte[] getMessage() {
+ byte result[];
+
+ result = new byte[getWordCount() * 2 + 1];
+
+ int offset = 0;
+ result[offset++] = (byte)byteCount;
+
+ for (Register register : registers) {
+ byte[] data = register.toBytes();
+
+ result[offset++] = data[0];
+ result[offset++] = data[1];
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsRequest.java
new file mode 100644
index 0000000..8b05d75
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsRequest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadSerialDiagnosticsRequest.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadSerialDiagnosticsRequest extends ModbusRequest {
+
+ // Message fields.
+ private int function;
+ private short data;
+
+ /**
+ * Constructs a new Diagnostics request
+ * instance.
+ */
+ public ReadSerialDiagnosticsRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_SERIAL_DIAGNOSTICS);
+ setDataLength(4);
+ }
+
+ /**
+ * getFunction -- Get the DIAGNOSTICS sub-function.
+ *
+ * @return int
+ */
+ public int getFunction() {
+ return function;
+ }
+
+ /**
+ * setFunction - Set the DIAGNOSTICS sub-function.
+ *
+ * @param function - DIAGNOSTICS command sub-function.
+ */
+ public void setFunction(int function) {
+ this.function = function;
+ data = 0;
+ }
+
+ /**
+ * getWordCount -- get the number of words in data.
+ * @return Number of words in the data
+ */
+ public int getWordCount() {
+ return 1;
+ }
+
+ /**
+ * getData
+ * @return the first data item
+ */
+ public int getData() {
+ return data;
+ }
+
+ /**
+ * setData -- Set the optional data value
+ * @param value Diagnostics value
+ */
+ public void setData(int value) {
+ data = (short)value;
+ }
+
+ /**
+ * getData -- Get the data item at the index.
+ *
+ * @param index - Unused, must be 0.
+ * @return Data at index 0
+ *
+ * @deprecated
+ */
+ public int getData(int index) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ return data;
+ }
+
+ /**
+ * setData -- Set the data item at the index
+ *
+ * @param index - Unused, must be 0.
+ * @param value - Optional data value for function.
+ *
+ * @deprecated
+ */
+ public void setData(int index, int value) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ data = (short)value;
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ ReadSerialDiagnosticsResponse response = new ReadSerialDiagnosticsResponse();
+
+ // Copy the sub-function code.
+ response.setFunction(getFunction());
+
+ return updateResponseWithHeader(response);
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- Read the function code and data value
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ function = din.readUnsignedShort();
+ data = (short)(din.readShort() & 0xFFFF);
+ }
+
+ /**
+ * getMessage -- Create the DIAGNOSTICS message paylaod.
+ * @return Response as byte array
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)(function >> 8);
+ result[1] = (byte)(function & 0xFF);
+ result[2] = (byte)(data >> 8);
+ result[3] = (byte)(data & 0xFF);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsResponse.java
new file mode 100644
index 0000000..9d2bb8d
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadSerialDiagnosticsResponse.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadSerialDiagnosticsResponse.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadSerialDiagnosticsResponse extends ModbusResponse {
+
+ // Message fields.
+ private int function;
+ private short data;
+
+ /**
+ * Constructs a new Diagnostics response
+ * instance.
+ */
+ public ReadSerialDiagnosticsResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_SERIAL_DIAGNOSTICS);
+ setDataLength(4);
+ }
+
+ /**
+ * getFunction -- Get the DIAGNOSTICS sub-function.
+ *
+ * @return Function code
+ */
+ public int getFunction() {
+ return function;
+ }
+
+ /**
+ * setFunction - Set the DIAGNOSTICS sub-function.
+ *
+ * @param function - DIAGNOSTICS command sub-function.
+ */
+ public void setFunction(int function) {
+ this.function = function;
+ data = 0;
+ }
+
+ /**
+ * getWordCount -- get the number of words in data.
+ * @return Number of words in the data
+ */
+ public int getWordCount() {
+ return 1;
+ }
+
+ /**
+ * getData
+ * @return the first data item
+ */
+ public int getData() {
+ return data;
+ }
+
+ /**
+ * setData -- Set the optional data value
+ * @param value optional data value
+ */
+ public void setData(int value) {
+ data = (short)value;
+ }
+
+ /**
+ * getData -- Get the data item at the index.
+ *
+ * @param index - Unused, must be 0.
+ * @return Data at index 0
+ *
+ * @deprecated
+ */
+ public int getData(int index) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ return data;
+ }
+
+ /**
+ * setData -- Set the data item at the index
+ *
+ * @param index - Unused, must be 0.
+ * @param value - Optional data value for function.
+ *
+ * @deprecated
+ */
+ public void setData(int index, int value) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ data = (short)value;
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- Read the function code and data value
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ function = din.readUnsignedShort();
+ data = (short)(din.readShort() & 0xFFFF);
+ }
+
+ /**
+ * getMessage -- Create the DIAGNOSTICS message paylaod.
+ * @return message paylaod
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)(function >> 8);
+ result[1] = (byte)(function & 0xFF);
+ result[2] = (byte)(data >> 8);
+ result[3] = (byte)(data & 0xFF);
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleRequest.java
new file mode 100644
index 0000000..98b0e23
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleRequest.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.NonWordDataHandler;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.*;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a Read / Write Multiple Registers request.
+ *
+ * @author Julie Haugh
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadWriteMultipleRequest extends ModbusRequest {
+ private NonWordDataHandler nonWordDataHandler;
+ private int readReference;
+ private int readCount;
+ private int writeReference;
+ private int writeCount;
+ private Register registers[];
+
+ /**
+ * Constructs a new Read/Write Multiple Registers Request instance.
+ * @param unit Unit ID
+ * @param readRef Register to read
+ * @param writeCount Number of registers to write
+ * @param writeRef Starting register to write
+ * @param readCount Number of registers to read
+ */
+ public ReadWriteMultipleRequest(int unit, int readRef, int readCount, int writeRef, int writeCount) {
+ super();
+
+ setUnitID(unit);
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+
+ // There is no additional data in this request.
+ setDataLength(9 + writeCount * 2);
+
+ readReference = readRef;
+ this.readCount = readCount;
+ writeReference = writeRef;
+ this.writeCount = writeCount;
+ registers = new Register[writeCount];
+ for (int i = 0; i < writeCount; i++) {
+ registers[i] = new SimpleRegister(0);
+ }
+ }
+
+ /**
+ * Constructs a new Read/Write Multiple Registers Request instance.
+ * @param unit Unit ID
+ */
+ public ReadWriteMultipleRequest(int unit) {
+ super();
+
+ setUnitID(unit);
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+
+ // There is no additional data in this request.
+ setDataLength(9);
+ }
+
+ /**
+ * Constructs a new Read/Write Multiple Registers Request instance.
+ */
+ public ReadWriteMultipleRequest() {
+ super();
+
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+
+ // There is no additional data in this request.
+ setDataLength(9);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReadWriteMultipleResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ ReadWriteMultipleResponse response;
+ InputRegister[] readRegs;
+ Register[] writeRegs;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ // 2. get input registers range
+ try {
+ // First the write
+ writeRegs = procimg.getRegisterRange(getWriteReference(), getWriteWordCount());
+ for (int i = 0; i < writeRegs.length; i++) {
+ writeRegs[i].setValue(getRegister(i).getValue());
+ }
+
+ // And then the read
+ readRegs = procimg.getRegisterRange(getReadReference(), getReadWordCount());
+ InputRegister[] dummy = new InputRegister[readRegs.length];
+ for (int i = 0; i < readRegs.length; i++) {
+ dummy[i] = new SimpleInputRegister(readRegs[i].getValue());
+ }
+ readRegs = dummy;
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (ReadWriteMultipleResponse)getResponse();
+ response.setRegisters(readRegs);
+
+ return response;
+ }
+ /**
+ * getReadReference - Returns the reference of the register to start writing
+ * to with this ReadWriteMultipleRequest.
+ *
+ *
+ * @return the reference of the register to start writing to as int
+ * .
+ */
+ public int getReadReference() {
+ return readReference;
+ }
+
+ /**
+ * setReadReference - Sets the reference of the register to writing to with
+ * this ReadWriteMultipleRequest.
+ *
+ *
+ * @param ref the reference of the register to start writing to as
+ * int.
+ */
+ public void setReadReference(int ref) {
+ readReference = ref;
+ }
+
+ /**
+ * getWriteReference - Returns the reference of the register to start
+ * writing to with this ReadWriteMultipleRequest.
+ *
+ *
+ * @return the reference of the register to start writing to as int
+ * .
+ */
+ public int getWriteReference() {
+ return writeReference;
+ }
+
+ /**
+ * setWriteReference - Sets the reference of the register to write to with
+ * this ReadWriteMultipleRequest.
+ *
+ *
+ * @param ref the reference of the register to start writing to as
+ * int.
+ */
+ public void setWriteReference(int ref) {
+ writeReference = ref;
+ }
+
+ /**
+ * getRegisters - Returns the registers to be written with this
+ * ReadWriteMultipleRequest.
+ *
+ *
+ * @return the registers to be read as Register[].
+ */
+ public synchronized Register[] getRegisters() {
+ Register[] dest = new Register[registers.length];
+ System.arraycopy(registers, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * setRegisters - Sets the registers to be written with this
+ * ReadWriteMultipleRequest.
+ *
+ *
+ * @param registers the registers to be written as Register[].
+ */
+ public void setRegisters(Register[] registers) {
+ writeCount = registers != null ? registers.length : 0;
+ this.registers = registers != null ? Arrays.copyOf(registers, registers.length) : null;
+ }
+
+ /**
+ * getRegister - Returns the specified Register.
+ *
+ * @param index the index of the Register.
+ *
+ * @return the register as Register.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public Register getRegister(int index) throws IndexOutOfBoundsException {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(index + " < 0");
+ }
+
+ if (index >= getWriteWordCount()) {
+ throw new IndexOutOfBoundsException(index + " > " + getWriteWordCount());
+ }
+
+ return registers[index];
+ }
+
+ /**
+ * getReadRegisterValue - Returns the value of the specified register
+ * interpreted as unsigned short.
+ *
+ * @param index the relative index of the register for which the value should
+ * be retrieved.
+ *
+ * @return the value as int.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int getReadRegisterValue(int index) throws IndexOutOfBoundsException {
+ return getRegister(index).toUnsignedShort();
+ }
+
+ /**
+ * getByteCount - Returns the number of bytes representing the values to be
+ * written.
+ *
+ * @return the number of bytes to be written as int.
+ */
+ public int getByteCount() {
+ return getWriteWordCount() * 2;
+ }
+
+ /**
+ * getWriteWordCount - Returns the number of words to be written.
+ *
+ * @return the number of words to be written as int.
+ */
+ public int getWriteWordCount() {
+ return writeCount;
+ }
+
+ /**
+ * setWriteWordCount - Sets the number of words to be written.
+ *
+ * @param count the number of words to be written as int.
+ */
+ public void setWriteWordCount(int count) {
+ writeCount = count;
+ }
+
+ /**
+ * getReadWordCount - Returns the number of words to be read.
+ *
+ * @return the number of words to be read as int.
+ */
+ public int getReadWordCount() {
+ return readCount;
+ }
+
+ /**
+ * setReadWordCount - Sets the number of words to be read.
+ *
+ * @param count the number of words to be read as int.
+ */
+ public void setReadWordCount(int count) {
+ readCount = count;
+ }
+
+ /**
+ * getNonWordDataHandler - Returns the actual non word data handler.
+ *
+ * @return the actual NonWordDataHandler.
+ */
+ public NonWordDataHandler getNonWordDataHandler() {
+ return nonWordDataHandler;
+ }
+
+ /**
+ * setNonWordDataHandler - Sets a non word data handler. A non-word data
+ * handler is responsible for converting words from a Modbus packet into the
+ * non-word values associated with the actual device's registers.
+ *
+ * @param dhandler a NonWordDataHandler instance.
+ */
+ public void setNonWordDataHandler(NonWordDataHandler dhandler) {
+ nonWordDataHandler = dhandler;
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- read the values of the registers to be written, along with
+ * the reference and count for the registers to be read.
+ */
+ public void readData(DataInput input) throws IOException {
+ readReference = input.readUnsignedShort();
+ readCount = input.readUnsignedShort();
+ writeReference = input.readUnsignedShort();
+ writeCount = input.readUnsignedShort();
+ int byteCount = input.readUnsignedByte();
+
+ if (nonWordDataHandler == null) {
+ byte buffer[] = new byte[byteCount];
+ input.readFully(buffer, 0, byteCount);
+
+ int offset = 0;
+ registers = new Register[writeCount];
+
+ for (int register = 0; register < writeCount; register++) {
+ registers[register] = new SimpleRegister(buffer[offset], buffer[offset + 1]);
+ offset += 2;
+ }
+ }
+ else {
+ nonWordDataHandler.readData(input, writeReference, writeCount);
+ }
+ }
+
+ /**
+ * getMessage -- return a prepared message.
+ * @return prepared message
+ */
+ public byte[] getMessage() {
+ byte results[] = new byte[9 + 2 * getWriteWordCount()];
+
+ results[0] = (byte)(readReference >> 8);
+ results[1] = (byte)(readReference & 0xFF);
+ results[2] = (byte)(readCount >> 8);
+ results[3] = (byte)(readCount & 0xFF);
+ results[4] = (byte)(writeReference >> 8);
+ results[5] = (byte)(writeReference & 0xFF);
+ results[6] = (byte)(writeCount >> 8);
+ results[7] = (byte)(writeCount & 0xFF);
+ results[8] = (byte)(writeCount * 2);
+
+ int offset = 9;
+ for (int i = 0; i < writeCount; i++) {
+ Register reg = getRegister(i);
+ byte[] bytes = reg.toBytes();
+
+ results[offset++] = bytes[0];
+ results[offset++] = bytes[1];
+ }
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleResponse.java
new file mode 100644
index 0000000..e61526a
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReadWriteMultipleResponse.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.InputRegister;
+import com.ghgande.j2mod.modbus.procimg.Register;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a ReadWriteMultipleResponse.
+ *
+ * @author Julie (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReadWriteMultipleResponse extends ModbusResponse {
+
+ private int byteCount;
+ private InputRegister[] registers;
+
+ /**
+ * Constructs a new ReadWriteMultipleResponse instance.
+ *
+ * @param registers the Register[] holding response registers.
+ */
+ public ReadWriteMultipleResponse(InputRegister[] registers) {
+ super();
+
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+ setDataLength(registers.length * 2 + 1);
+
+ this.registers = Arrays.copyOf(registers, registers.length);
+ byteCount = registers.length * 2;
+ }
+
+ /**
+ * Constructs a new ReadWriteMultipleResponse instance.
+ *
+ * @param count the number of Register[] holding response registers.
+ */
+ public ReadWriteMultipleResponse(int count) {
+ super();
+
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+ setDataLength(count * 2 + 1);
+
+ registers = new InputRegister[count];
+ byteCount = count * 2;
+ }
+
+ /**
+ * Constructs a new ReadWriteMultipleResponse instance.
+ */
+ public ReadWriteMultipleResponse() {
+ super();
+
+ setFunctionCode(Modbus.READ_WRITE_MULTIPLE);
+ }
+
+ /**
+ * Returns the number of bytes that have been read.
+ *
+ * @return the number of bytes that have been read as int.
+ */
+ public int getByteCount() {
+ return byteCount;
+ }
+
+ /**
+ * Returns the number of words that have been read. The returned value
+ * should be half of the the byte count of this
+ * ReadWriteMultipleResponse.
+ *
+ * @return the number of words that have been read as int.
+ */
+ public int getWordCount() {
+ return byteCount / 2;
+ }
+
+ /**
+ * Returns the Register at the given position (relative to the
+ * reference used in the request).
+ *
+ * @param index the relative index of the InputRegister.
+ *
+ * @return the register as InputRegister.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public InputRegister getRegister(int index) {
+ if (registers == null) {
+ throw new IndexOutOfBoundsException("No registers defined!");
+ }
+
+ if (index < 0) {
+ throw new IndexOutOfBoundsException("Negative index: " + index);
+ }
+
+ if (index >= getWordCount()) {
+ throw new IndexOutOfBoundsException(index + " > " + getWordCount());
+ }
+
+ return registers[index];
+ }
+
+ /**
+ * Returns the value of the register at the given position (relative to the
+ * reference used in the request) interpreted as unsigned short.
+ *
+ * @param index the relative index of the register for which the value should
+ * be retrieved.
+ *
+ * @return the value as int.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int getRegisterValue(int index) throws IndexOutOfBoundsException {
+ return getRegister(index).toUnsignedShort();
+ }
+
+ /**
+ * Returns the reference to the array of registers read.
+ *
+ * @return a InputRegister[] instance.
+ */
+ public synchronized InputRegister[] getRegisters() {
+ InputRegister[] dest = new InputRegister[registers.length];
+ System.arraycopy(registers, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Sets the entire block of registers for this response
+ * @param registers Array of registers
+ */
+ public void setRegisters(InputRegister[] registers) {
+ byteCount = registers.length * 2;
+ setDataLength(byteCount + 1);
+
+ this.registers = Arrays.copyOf(registers, registers.length);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeByte(byteCount);
+
+ for (int k = 0; k < getWordCount(); k++) {
+ dout.write(registers[k].toBytes());
+ }
+ }
+
+ public void readData(DataInput din) throws IOException {
+ byteCount = din.readUnsignedByte();
+
+ registers = new Register[getWordCount()];
+
+ for (int k = 0; k < getWordCount(); k++) {
+ registers[k] = new SimpleRegister(din.readByte(), din.readByte());
+ }
+
+ setDataLength(byteCount + 1);
+ }
+
+ public byte[] getMessage() {
+ byte result[];
+
+ result = new byte[getWordCount() * 2 + 1];
+
+ int offset = 0;
+ result[offset++] = (byte)byteCount;
+
+ for (InputRegister register : registers) {
+ byte[] data = register.toBytes();
+
+ result[offset++] = data[0];
+ result[offset++] = data[1];
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDRequest.java
new file mode 100644
index 0000000..c5d6cd6
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Read MEI Data request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReportSlaveIDRequest extends ModbusRequest {
+
+ /**
+ * Constructs a new Report Slave ID request
+ * instance.
+ */
+ public ReportSlaveIDRequest() {
+ super();
+
+ setFunctionCode(Modbus.REPORT_SLAVE_ID);
+
+ // There is no additional data in this request.
+ setDataLength(0);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new ReportSlaveIDResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ return createExceptionResponse(Modbus.ILLEGAL_FUNCTION_EXCEPTION);
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- dummy function. There is no data with the request.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ }
+
+ /**
+ * getMessage
+ * @return an empty array as there is no data for this request
+ */
+ public byte[] getMessage() {
+
+ return new byte[0];
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDResponse.java
new file mode 100644
index 0000000..eaf41ee
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ReportSlaveIDResponse.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a ReadMEIResponse.
+ *
+ * Derived from similar class for Read Coils response.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ReportSlaveIDResponse extends ModbusResponse {
+
+ // Message fields.
+ int m_length;
+ byte m_data[];
+ int m_status;
+ int m_slaveId;
+
+ /**
+ * Constructs a new ReportSlaveIDResponse
+ * instance.
+ */
+ public ReportSlaveIDResponse() {
+ super();
+ setFunctionCode(Modbus.REPORT_SLAVE_ID);
+ }
+
+ /**
+ * getSlaveID -- return the slave identifier field.
+ * @return slave identifier field
+ */
+ public int getSlaveID() {
+ return m_slaveId;
+ }
+
+ /**
+ * setSlaveID -- initialize the slave identifier when constructing
+ * a response message.
+ * @param unitID UnitID of the slave
+ */
+ public void setSlaveID(int unitID) {
+ m_slaveId = unitID;
+ }
+
+ /**
+ * getStatus -- get the slave's "run" status.
+ *
+ * @return boolean
+ */
+ public boolean getStatus() {
+ return m_status != 0;
+ }
+
+ /**
+ * setStatus -- initialize the slave's "run" status when constructing
+ * a response message.
+ *
+ * @param b Status value
+ */
+ public void setStatus(boolean b) {
+ m_status = b ? 0xff : 0x00;
+ }
+
+ /**
+ * getData -- get the device-depending data for the slave.
+ *
+ * @return byte array
+ */
+ public byte[] getData() {
+ byte[] result = new byte[m_length - 2];
+ System.arraycopy(m_data, 0, result, 0, m_length - 2);
+
+ return result;
+ }
+
+ /**
+ * setData -- initialize the slave's device dependent data when
+ * initializing a response.
+ *
+ * @param data byte array
+ */
+ public void setData(byte[] data) {
+ // There are always two bytes of payload in the message -- the
+ // slave ID and the run status indicator.
+ if (data == null) {
+ m_length = 2;
+ m_data = new byte[0];
+
+ return;
+ }
+
+ if (data.length > 249) {
+ throw new IllegalArgumentException("data length limit exceeded");
+ }
+
+ m_length = data.length + 2;
+
+ m_data = new byte[data.length];
+ System.arraycopy(data, 0, m_data, 0, data.length);
+ }
+
+ /**
+ * writeData -- output the completed Modbus message to dout
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- input the Modbus message from din. If there was a
+ * header, such as for Modbus/TCP, it will have been read
+ * already.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+
+ // Get the size of any device-specific data.
+ m_length = din.readUnsignedByte();
+ if (m_length < 2 || m_length > 255) {
+ return;
+ }
+
+ // Get the run status and device identifier.
+ m_slaveId = din.readUnsignedByte();
+ m_status = din.readUnsignedByte();
+
+ /*
+ * The device-specific data is two bytes shorter than the
+ * length read previously. That length includes the run status
+ * and slave ID.
+ */
+ m_data = new byte[m_length - 2];
+ if (m_length > 2) {
+ din.readFully(m_data, 0, m_length - 2);
+ }
+ }
+
+ /**
+ * getMessage -- format the message into a byte array.
+ * @return Byte array of message
+ */
+ public byte[] getMessage() {
+ byte result[] = new byte[3 + m_length];
+ int offset = 0;
+
+ result[offset++] = (byte)(m_length + 2);
+ result[offset++] = (byte)m_slaveId;
+ result[offset++] = (byte)m_status;
+ if (m_length > 0) {
+ System.arraycopy(m_data, 0, result, offset, m_length - 2);
+ }
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilRequest.java
new file mode 100644
index 0000000..67ac0af
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilRequest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.DigitalOut;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteCoilRequest. The implementation directly
+ * correlates with the class 0 function write coil (FC 5). It
+ * encapsulates the corresponding request message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteCoilRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private boolean coil;
+
+ /**
+ * Constructs a new WriteCoilRequest instance.
+ */
+ public WriteCoilRequest() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_COIL);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new WriteCoilRequest instance with a given
+ * reference and state to be written.
+ *
+ * @param ref the reference number of the register to read from.
+ * @param b true if the coil should be set of false if it should be unset.
+ */
+ public WriteCoilRequest(int ref, boolean b) {
+ super();
+
+ setFunctionCode(Modbus.WRITE_COIL);
+ setDataLength(4);
+
+ setReference(ref);
+ setCoil(b);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new WriteCoilResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ WriteCoilResponse response;
+ DigitalOut dout;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get coil
+ try {
+ dout = procimg.getDigitalOut(getReference());
+ // 3. set coil
+ dout.set(getCoil());
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (WriteCoilResponse)getResponse();
+ response.setReference(getReference());
+ response.setCoil(getCoil());
+
+ return response;
+ }
+
+ /**
+ * Returns the reference of the register of the coil that should be written
+ * to with this ReadCoilsRequest.
+ *
+ * @return the reference of the coil's register.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register of the coil that should be written to
+ * with this ReadCoilsRequest.
+ *
+ *
+ * @param ref the reference of the coil's register.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the state that should be written with this
+ * WriteCoilRequest.
+ *
+ * @return true if the coil should be set of false if it should be unset.
+ */
+ public boolean getCoil() {
+ return coil;
+ }
+
+ /**
+ * Sets the state that should be written with this WriteCoilRequest.
+ *
+ * @param b true if the coil should be set of false if it should be unset.
+ */
+ public void setCoil(boolean b) {
+ coil = b;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+
+ if (coil) {
+ dout.write(Modbus.COIL_ON_BYTES, 0, 2);
+ }
+ else {
+ dout.write(Modbus.COIL_OFF_BYTES, 0, 2);
+ }
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+
+ if (din.readByte() == Modbus.COIL_ON) {
+ coil = true;
+ }
+ else {
+ coil = false;
+ }
+
+ // discard the next byte.
+ din.readByte();
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ if (coil) {
+ result[2] = Modbus.COIL_ON_BYTES[0];
+ result[3] = Modbus.COIL_ON_BYTES[1];
+ }
+ else {
+ result[2] = Modbus.COIL_OFF_BYTES[0];
+ result[3] = Modbus.COIL_OFF_BYTES[1];
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilResponse.java
new file mode 100644
index 0000000..81702c4
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteCoilResponse.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteCoilResponse. The implementation directly
+ * correlates with the class 0 function write coil (FC 5). It
+ * encapsulates the corresponding response message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteCoilResponse extends ModbusResponse {
+ private boolean coil = false;
+ private int reference;
+
+ /**
+ * Constructs a new WriteCoilResponse instance.
+ */
+ public WriteCoilResponse() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_COIL);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new WriteCoilResponse instance.
+ *
+ * @param reference the offset were writing was started from.
+ * @param b the state of the coil; true set, false reset.
+ */
+ public WriteCoilResponse(int reference, boolean b) {
+ super();
+
+ setFunctionCode(Modbus.WRITE_COIL);
+ setDataLength(4);
+
+ setReference(reference);
+ setCoil(b);
+ }
+
+ /**
+ * Gets the state that has been returned in this WriteCoilRequest.
+ *
+ * @return true if the coil is set, false if unset.
+ */
+ public boolean getCoil() {
+ return coil;
+ }
+
+ /**
+ * Sets the state that has been returned in the raw response.
+ *
+ * @param b true if the coil should be set of false if it should be unset.
+ */
+ public void setCoil(boolean b) {
+ coil = b;
+ }
+
+ /**
+ * Returns the reference of the register of the coil that has been written
+ * to with the request.
+ *
+ *
+ * @return the reference of the coil's register.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register of the coil that has been written to
+ * with the request.
+ *
+ *
+ * @param ref the reference of the coil's register.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ byte data[] = new byte[4];
+ din.readFully(data);
+
+ setReference(((data[0] << 8) | (data[1] & 0xff)));
+ setCoil(data[2] == Modbus.COIL_ON);
+
+ setDataLength(4);
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ if (coil) {
+ result[2] = Modbus.COIL_ON_BYTES[0];
+ result[3] = Modbus.COIL_ON_BYTES[1];
+ }
+ else {
+ result[2] = Modbus.COIL_OFF_BYTES[0];
+ result[3] = Modbus.COIL_OFF_BYTES[1];
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordRequest.java
new file mode 100644
index 0000000..413a745
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordRequest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.msg.WriteFileRecordResponse.RecordResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.*;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a Write File Record request.
+ *
+ * @author Julie Haugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteFileRecordRequest extends ModbusRequest {
+ private RecordRequest[] records;
+
+ /**
+ * Constructs a new Write File Record request
+ * instance.
+ */
+ public WriteFileRecordRequest() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_FILE_RECORD);
+
+ // Set up space for the initial header.
+ setDataLength(1);
+ }
+
+ /**
+ * getRequestSize -- return the total request size. This is useful
+ * for determining if a new record can be added.
+ *
+ * @return size in bytes of response.
+ */
+ public int getRequestSize() {
+ if (records == null) {
+ return 1;
+ }
+
+ int size = 1;
+ for (RecordRequest record : records) {
+ size += record.getRequestSize();
+ }
+
+ return size;
+ }
+
+ /**
+ * getRequestCount -- return the number of record requests in this
+ * message.
+ * @return number of record requests in this message
+ */
+ public int getRequestCount() {
+ if (records == null) {
+ return 0;
+ }
+
+ return records.length;
+ }
+
+ /**
+ * getRecord -- return the record request indicated by the reference
+ * @param reference Register reference
+ * @return the record request indicated by the reference
+ */
+ public RecordRequest getRecord(int reference) {
+ return records[reference];
+ }
+
+ /**
+ * addRequest -- add a new record request.
+ * @param request Request record
+ */
+ public void addRequest(RecordRequest request) {
+ if (request.getRequestSize() + getRequestSize() > 248) {
+ throw new IllegalArgumentException();
+ }
+
+ if (records == null) {
+ records = new RecordRequest[1];
+ }
+ else {
+ RecordRequest old[] = records;
+ records = new RecordRequest[old.length + 1];
+
+ System.arraycopy(old, 0, records, 0, old.length);
+ }
+ records[records.length - 1] = request;
+
+ setDataLength(getRequestSize());
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new WriteFileRecordResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ WriteFileRecordResponse response = (WriteFileRecordResponse)getResponse();
+
+ // Get the process image.
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ // There is a list of requests to be resolved.
+ try {
+ for (int i = 0; i < getRequestCount(); i++) {
+ RecordRequest recordRequest = getRecord(i);
+ if (recordRequest.getFileNumber() < 0 || recordRequest.getFileNumber() >= procimg.getFileCount()) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ File file = procimg.getFileByNumber(recordRequest.getFileNumber());
+
+ if (recordRequest.getRecordNumber() < 0 || recordRequest.getRecordNumber() >= file.getRecordCount()) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ Record record = file.getRecord(recordRequest.getRecordNumber());
+ int registers = recordRequest.getWordCount();
+ if (record == null && registers != 0) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ short data[] = new short[registers];
+ for (int j = 0; j < registers; j++) {
+ Register register = record.getRegister(j);
+ if (register == null) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+
+ register.setValue(recordRequest.getRegister(j).getValue());
+ data[j] = recordRequest.getRegister(j).toShort();
+ }
+ RecordResponse recordResponse = new RecordResponse(file.getFileNumber(), record == null ? 0 : record.getRecordNumber(), data);
+ response.addResponse(recordResponse);
+ }
+ }
+ catch (IllegalAddressException e) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ return response;
+ }
+
+ /**
+ * writeData -- output this Modbus message to dout.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ /**
+ * readData -- convert the byte stream into a request.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+ int byteCount = din.readUnsignedByte();
+
+ records = new RecordRequest[0];
+
+ for (int offset = 1; offset + 7 < byteCount; ) {
+ int function = din.readUnsignedByte();
+ int file = din.readUnsignedShort();
+ int record = din.readUnsignedShort();
+ int count = din.readUnsignedShort();
+
+ offset += 7;
+
+ if (function != 6) {
+ throw new IOException();
+ }
+
+ if (record < 0 || record >= 10000) {
+ throw new IOException();
+ }
+
+ if (count < 0 || count >= 126) {
+ throw new IOException();
+ }
+
+ short registers[] = new short[count];
+ for (int j = 0; j < count; j++) {
+ registers[j] = din.readShort();
+ offset += 2;
+ }
+ RecordRequest dummy[] = new RecordRequest[records.length + 1];
+ if (records.length > 0) {
+ System.arraycopy(records, 0, dummy, 0, records.length);
+ }
+
+ records = dummy;
+ records[records.length - 1] = new RecordRequest(file, record, registers);
+ }
+ }
+
+ /**
+ * getMessage -- return the raw binary message.
+ * @return the raw binary message
+ */
+ public byte[] getMessage() {
+ byte results[] = new byte[getRequestSize()];
+
+ results[0] = (byte)(getRequestSize() - 1);
+
+ int offset = 1;
+ for (RecordRequest record : records) {
+ record.getRequest(results, offset);
+ offset += record.getRequestSize();
+ }
+ return results;
+ }
+
+ public static class RecordRequest {
+ private int fileNumber;
+ private int recordNumber;
+ private int wordCount;
+ private byte data[];
+
+ public RecordRequest(int file, int record, short[] values) {
+ fileNumber = file;
+ recordNumber = record;
+ wordCount = values.length;
+ data = new byte[wordCount * 2];
+
+ int offset = 0;
+ for (int i = 0; i < wordCount; i++) {
+ data[offset++] = (byte)(values[i] >> 8);
+ data[offset++] = (byte)(values[i] & 0xFF);
+ }
+ }
+
+ public int getFileNumber() {
+ return fileNumber;
+ }
+
+ public int getRecordNumber() {
+ return recordNumber;
+ }
+
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ public SimpleRegister getRegister(int register) {
+ if (register < 0 || register >= wordCount) {
+ throw new IllegalAddressException("0 <= " + register + " < " + wordCount);
+ }
+ byte b1 = data[register * 2];
+ byte b2 = data[register * 2 + 1];
+
+ return new SimpleRegister(b1, b2);
+ }
+
+ /**
+ * getRequestSize -- return the size of the response in bytes.
+ * @return the size of the response in bytes
+ */
+ public int getRequestSize() {
+ return 7 + wordCount * 2;
+ }
+
+ public void getRequest(byte[] request, int offset) {
+ request[offset++] = 6;
+ request[offset++] = (byte)(fileNumber >> 8);
+ request[offset++] = (byte)(fileNumber & 0xFF);
+ request[offset++] = (byte)(recordNumber >> 8);
+ request[offset++] = (byte)(recordNumber & 0xFF);
+ request[offset++] = (byte)(wordCount >> 8);
+ request[offset++] = (byte)(wordCount & 0xFF);
+
+ System.arraycopy(data, 0, request, offset, data.length);
+ }
+
+ public byte[] getRequest() {
+ byte[] request = new byte[7 + 2 * wordCount];
+
+ getRequest(request, 0);
+
+ return request;
+ }
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordResponse.java
new file mode 100644
index 0000000..3b3d5bc
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteFileRecordResponse.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteFileRecordResponse.
+ *
+ * @author Julie
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteFileRecordResponse extends ModbusResponse {
+ private RecordResponse[] records;
+
+ /**
+ * Constructs a new WriteFileRecordResponse instance.
+ */
+ public WriteFileRecordResponse() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_FILE_RECORD);
+ setDataLength(7);
+ }
+
+ /**
+ * getRequestSize -- return the total request size. This is useful
+ * for determining if a new record can be added.
+ *
+ * @return size in bytes of response.
+ */
+ public int getResponseSize() {
+ if (records == null) {
+ return 1;
+ }
+
+ int size = 1;
+ for (RecordResponse record : records) {
+ size += record.getResponseSize();
+ }
+
+ return size;
+ }
+
+ /**
+ * getRequestCount -- return the number of record requests in this
+ * message.
+ * @return the number of record requests in this message
+ */
+ public int getRequestCount() {
+ if (records == null) {
+ return 0;
+ }
+
+ return records.length;
+ }
+
+ /**
+ * getRecord -- return the record request indicated by the reference
+ * @param index Record to get
+ * @return the record request indicated by the reference
+ */
+ public RecordResponse getRecord(int index) {
+ return records[index];
+ }
+
+ /**
+ * addResponse -- add a new record response.
+ * @param response Add record response
+ */
+ public void addResponse(RecordResponse response) {
+ if (response.getResponseSize() + getResponseSize() > 248) {
+ throw new IllegalArgumentException();
+ }
+
+ if (records == null) {
+ records = new RecordResponse[1];
+ }
+ else {
+ RecordResponse old[] = records;
+ records = new RecordResponse[old.length + 1];
+
+ System.arraycopy(old, 0, records, 0, old.length);
+ }
+ records[records.length - 1] = response;
+
+ setDataLength(getResponseSize());
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ int byteCount = din.readUnsignedByte();
+
+ records = new RecordResponse[0];
+
+ for (int offset = 1; offset + 7 < byteCount; ) {
+ int function = din.readUnsignedByte();
+ int file = din.readUnsignedShort();
+ int record = din.readUnsignedShort();
+ int count = din.readUnsignedShort();
+
+ offset += 7;
+
+ if (function != 6) {
+ throw new IOException();
+ }
+
+ if (record < 0 || record >= 10000) {
+ throw new IOException();
+ }
+
+ if (count < 0 || count >= 126) {
+ throw new IOException();
+ }
+
+ short registers[] = new short[count];
+ for (int j = 0; j < count; j++) {
+ registers[j] = din.readShort();
+ offset += 2;
+ }
+ RecordResponse dummy[] = new RecordResponse[records.length + 1];
+ if (records.length > 0) {
+ System.arraycopy(records, 0, dummy, 0, records.length);
+ }
+
+ records = dummy;
+ records[records.length - 1] = new RecordResponse(file, record, registers);
+ }
+ }
+
+ public byte[] getMessage() {
+ byte results[] = new byte[getResponseSize()];
+
+ results[0] = (byte)(getResponseSize() - 1);
+
+ int offset = 1;
+ for (RecordResponse record : records) {
+ record.getResponse(results, offset);
+ offset += record.getResponseSize();
+ }
+ return results;
+ }
+
+ public static class RecordResponse {
+ private int fileNumber;
+ private int recordNumber;
+ private int wordCount;
+ private byte data[];
+
+ public RecordResponse(int file, int record, short[] values) {
+ fileNumber = file;
+ recordNumber = record;
+ wordCount = values.length;
+ data = new byte[wordCount * 2];
+
+ int offset = 0;
+ for (int i = 0; i < wordCount; i++) {
+ data[offset++] = (byte)(values[i] >> 8);
+ data[offset++] = (byte)(values[i] & 0xFF);
+ }
+ }
+
+ public int getFileNumber() {
+ return fileNumber;
+ }
+
+ public int getRecordNumber() {
+ return recordNumber;
+ }
+
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ public SimpleRegister getRegister(int register) {
+ if (register < 0 || register >= wordCount) {
+ throw new IndexOutOfBoundsException("0 <= " + register + " < " + wordCount);
+ }
+ byte b1 = data[register * 2];
+ byte b2 = data[register * 2 + 1];
+
+ return new SimpleRegister(b1, b2);
+ }
+
+ /**
+ * getResponseSize -- return the size of the response in bytes.
+ * @return the size of the response in bytes
+ */
+ public int getResponseSize() {
+ return 7 + wordCount * 2;
+ }
+
+ public void getResponse(byte[] response, int offset) {
+ response[offset++] = 6;
+ response[offset++] = (byte)(fileNumber >> 8);
+ response[offset++] = (byte)(fileNumber & 0xFF);
+ response[offset++] = (byte)(recordNumber >> 8);
+ response[offset++] = (byte)(recordNumber & 0xFF);
+ response[offset++] = (byte)(wordCount >> 8);
+ response[offset++] = (byte)(wordCount & 0xFF);
+
+ System.arraycopy(data, 0, response, offset, data.length);
+ }
+
+ public byte[] getResponse() {
+ byte[] response = new byte[7 + 2 * wordCount];
+
+ getResponse(response, 0);
+
+ return response;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsRequest.java
new file mode 100644
index 0000000..4658cf6
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsRequest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.DigitalOut;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.util.BitVector;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteMultipleCoilsRequest. The implementation
+ * directly correlates with the class 1 function write multiple coils (FC
+ * 15). It encapsulates the corresponding request message.
+ *
+ *
+ * Coils are understood as bits that can be manipulated (i.e. set or cleared).
+ *
+ * A minimal message contains the reference to the first coil as a
+ * short, the number of coils as a short, and not less
+ * than one byte of coil data.
+ */
+ public WriteMultipleCoilsRequest() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_MULTIPLE_COILS);
+ setDataLength(5);
+
+ coils = new BitVector(1);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new WriteMultipleCoilsResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ WriteMultipleCoilsResponse response;
+ DigitalOut douts[];
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get coil range
+ try {
+ douts = procimg.getDigitalOutRange(reference, coils.size());
+ // 3. set coils
+ for (int i = 0; i < douts.length; i++) {
+ douts[i].set(coils.getBit(i));
+ }
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (WriteMultipleCoilsResponse)getResponse();
+ response.setBitCount(coils.size());
+ response.setReference(reference);
+
+ return response;
+ }
+
+ /**
+ * getReference - Returns the reference of the coil to to start writing to
+ * with this WriteMultipleCoilsRequest.
+ *
+ * @return the reference of the coil to start writing to as an int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference - Sets the reference of the coil to start writing to with
+ * this WriteMultipleCoilsRequest.
+ *
+ * @param ref the reference of the coil to start writing to.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * getBitCount - Returns the number of coils written with the request.
+ *
+ * @return the number of coils that have been written.
+ */
+ public int getBitCount() {
+ if (coils == null) {
+ return 0;
+ }
+ else {
+ return coils.size();
+ }
+ }
+
+ /**
+ * getByteCount - Returns the number of bytes required for packing the
+ * coils.
+ *
+ * @return the number of bytes required for packing the coils.
+ */
+ public int getByteCount() {
+ return coils.byteSize();
+ }
+
+ /**
+ * getCoilStatus - Returns the status of the specified coil.
+ *
+ * @param index the index of the coil to be tested.
+ *
+ * @return true if set, false otherwise.
+ *
+ * @throws IndexOutOfBoundsException if the given index is out of bounds.
+ */
+ public boolean getCoilStatus(int index) throws IndexOutOfBoundsException {
+ return coils.getBit(index);
+ }
+
+ /**
+ * setCoilStatus - Sets the status of the specified coil.
+ *
+ * @param index the index of the coil to be set/reset.
+ * @param b true if to be set, false for reset.
+ *
+ * @throws IndexOutOfBoundsException if the given index is out of bounds.
+ */
+ public void setCoilStatus(int index, boolean b) throws IndexOutOfBoundsException {
+ coils.setBit(index, b);
+ }
+
+ /**
+ * getCoils - Returns the BitVector instance holding coil status
+ * information.
+ *
+ * @return the coils status as a BitVector instance.
+ */
+ public BitVector getCoils() {
+ return coils;
+ }
+
+ /**
+ * setCoils - Sets the BitVector instance holding coil status
+ * information.
+ *
+ * @param bv a BitVector instance holding coil status info.
+ */
+ public void setCoils(BitVector bv) {
+ coils = bv;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+ dout.writeShort(coils.size());
+
+ dout.writeByte(coils.byteSize());
+ dout.write(coils.getBytes());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ int bitcount = din.readUnsignedShort();
+ int coilBytes = din.readUnsignedByte();
+ byte[] data = new byte[coilBytes];
+
+ for (int k = 0; k < coilBytes; k++) {
+ data[k] = din.readByte();
+ }
+
+ // decode bytes into BitVector, sets data and bitcount
+ coils = BitVector.createBitVector(data, bitcount);
+
+ // update data length
+ setDataLength(coilBytes + 5);
+ }
+
+ public byte[] getMessage() {
+ int len = coils.byteSize() + 5;
+ byte result[] = new byte[len];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+
+ result[2] = (byte)((coils.size() >> 8) & 0xff);
+ result[3] = (byte)(coils.size() & 0xff);
+
+ result[4] = (byte)coils.byteSize();
+
+ System.arraycopy(coils.getBytes(), 0, result, 5, coils.byteSize());
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsResponse.java
new file mode 100644
index 0000000..7288618
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleCoilsResponse.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteMultipleCoilsResponse. The implementation
+ * directly correlates with the class 1 function write multiple coils (FC
+ * 15). It encapsulates the corresponding response message.
+ *
+ * Coils are understood as bits that can be manipulated (i.e. set or cleared).
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteMultipleCoilsResponse extends ModbusResponse {
+
+ // instance attributes
+ private int reference;
+ private int bitCount;
+
+ /**
+ * Constructs a new WriteMultipleCoilsResponse instance with a
+ * given count of coils and starting reference.
+ *
+ *
+ * @param ref the offset to begin writing from.
+ * @param count the number of coils to be written.
+ */
+ public WriteMultipleCoilsResponse(int ref, int count) {
+ super();
+ reference = ref;
+ bitCount = count;
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new WriteMultipleCoilsResponse instance.
+ */
+ public WriteMultipleCoilsResponse() {
+ super();
+ setDataLength(4);
+ }
+
+ /**
+ * getReference - Returns the reference of the coil to start reading from
+ * with this WriteMultipleCoilsResponse.
+ *
+ *
+ * @return the reference of the coil to start reading from as int.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference - Sets the reference to the coil that is the first coil in
+ * this response.
+ *
+ * @param ref Rgister address of coil
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * getBitCount - Returns the quantity of coils written with the request.
+ *
+ *
+ * @return the quantity of coils that have been written.
+ */
+ public int getBitCount() {
+ return bitCount;
+ }
+
+ /**
+ * setBitCount - Sets the number of coils that will be in a response.
+ *
+ * @param count the number of coils in the response.
+ */
+ public void setBitCount(int count) {
+ bitCount = count;
+ }
+
+ /**
+ * writeData - Copy the attribute values for this message to the output
+ * buffer.
+ * @throws IOException If the data cannot be written
+ */
+ public void writeData(DataOutput dout) throws IOException {
+
+ dout.writeShort(reference);
+ dout.writeShort(bitCount);
+ }
+
+ /**
+ * readData - Initialize the attribute values for this message from the
+ * input buffer.
+ * @throws IOException If the data cannot be read
+ */
+ public void readData(DataInput din) throws IOException {
+
+ reference = din.readUnsignedShort();
+ bitCount = din.readUnsignedShort();
+ }
+
+ public byte[] getMessage() {
+ byte results[] = new byte[4];
+
+ results[0] = (byte)((reference >> 8) & 0xff);
+ results[1] = (byte)(reference & 0xff);
+ results[2] = (byte)((bitCount >> 8) & 0xff);
+ results[3] = (byte)(bitCount & 0xff);
+
+ return results;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersRequest.java
new file mode 100644
index 0000000..6d7c32e
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersRequest.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.NonWordDataHandler;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.Register;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Class implementing a WriteMultipleRegistersRequest. The
+ * implementation directly correlates with the class 0 function write
+ * multiple registers (FC 16). It encapsulates the corresponding request
+ * message.
+ *
+ * @author Dieter Wimberger
+ * @author jfhaugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteMultipleRegistersRequest extends ModbusRequest {
+ private int reference;
+ private Register[] registers;
+ private NonWordDataHandler nonWordDataHandler = null;
+
+ /**
+ * Constructs a new WriteMultipleRegistersRequest instance with a
+ * given starting reference and values to be written.
+ *
+ *
+ * @param first -- the address of the first register to write to.
+ * @param registers -- the registers to be written.
+ */
+ public WriteMultipleRegistersRequest(int first, Register[] registers) {
+ setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
+
+ setReference(first);
+ setRegisters(registers);
+ }
+
+ /**
+ * Constructs a new WriteMultipleRegistersRequest instance.
+ */
+ public WriteMultipleRegistersRequest() {
+ setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new WriteMultipleRegistersResponse());
+ }
+
+ /**
+ * createResponse - Returns the WriteMultipleRegistersResponse that
+ * represents the answer to this WriteMultipleRegistersRequest.
+ *
+ * The implementation should take care about assembling the reply to this
+ * WriteMultipleRegistersRequest.
+ *
+ * This method is used to create responses from the process image associated
+ * with the listener. It is commonly used to implement Modbus
+ * slave instances.
+ *
+ * @return the corresponding ModbusResponse.
+ *
+ *
+ * createResponse() must be able to handle the case where the word
+ * data that is in the response is actually non-word data. That is,
+ * where the slave device has data which are not actually
+ * short values in the range of registers being processed.
+ */
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ WriteMultipleRegistersResponse response;
+
+ if (nonWordDataHandler == null) {
+ Register[] regs;
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+ // 2. get registers
+ try {
+ regs = procimg.getRegisterRange(getReference(), getWordCount());
+ // 3. set Register values
+ for (int i = 0; i < regs.length; i++) {
+ regs[i].setValue(this.getRegister(i).getValue());
+ }
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ response = (WriteMultipleRegistersResponse)getResponse();
+ response.setReference(getReference());
+ response.setWordCount(getWordCount());
+ }
+ else {
+ int result = nonWordDataHandler.commitUpdate();
+ if (result > 0) {
+ return createExceptionResponse(result);
+ }
+
+ response = (WriteMultipleRegistersResponse)getResponse();
+ response.setReference(getReference());
+ response.setWordCount(nonWordDataHandler.getWordCount());
+ }
+
+ return response;
+ }
+
+ /**
+ * setReference - Returns the reference of the register to start writing to
+ * with this WriteMultipleRegistersRequest.
+ *
+ *
+ * @return the reference of the register to start writing to as int
+ * .
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * setReference - Sets the reference of the register to write to with this
+ * WriteMultipleRegistersRequest.
+ *
+ *
+ * @param ref the reference of the register to start writing to as an
+ * int.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * getRegisters - Returns the registers to be written with this
+ * WriteMultipleRegistersRequest.
+ *
+ *
+ * @return the registers to be written as Register[].
+ */
+ public synchronized Register[] getRegisters() {
+ Register[] dest = new Register[registers.length];
+ System.arraycopy(registers, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * setRegisters - Sets the registers to be written with this
+ * WriteMultipleRegistersRequest.
+ *
+ *
+ * @param registers the registers to be written as Register[].
+ */
+ public void setRegisters(Register[] registers) {
+ this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
+ }
+
+ /**
+ * getRegister - Returns the Register at the given position.
+ *
+ * @param index the relative index of the Register.
+ *
+ * @return the register as Register.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public Register getRegister(int index) throws IndexOutOfBoundsException {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(index + " < 0");
+ }
+
+ if (index >= getWordCount()) {
+ throw new IndexOutOfBoundsException(index + " > " + getWordCount());
+ }
+
+ return registers[index];
+ }
+
+ /**
+ * getRegisterValue - Returns the value of the specified register.
+ *
+ *
+ * @param index the index of the desired register.
+ *
+ * @return the value as an int.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public int getRegisterValue(int index) throws IndexOutOfBoundsException {
+ return getRegister(index).toUnsignedShort();
+ }
+
+ /**
+ * getByteCount - Returns the number of bytes representing the values to be
+ * written.
+ *
+ *
+ * @return the number of bytes to be written as int.
+ */
+ public int getByteCount() {
+ return getWordCount() * 2;
+ }
+
+ /**
+ * getWordCount - Returns the number of words to be written.
+ *
+ * @return the number of words to be written as int.
+ */
+ public int getWordCount() {
+ if (registers == null) {
+ return 0;
+ }
+
+ return registers.length;
+ }
+
+ /**
+ * getNonWordDataHandler - Returns the actual non word data handler.
+ *
+ * @return the actual NonWordDataHandler.
+ */
+ public NonWordDataHandler getNonWordDataHandler() {
+ return nonWordDataHandler;
+ }
+
+ /**
+ * setNonWordHandler - Sets a non word data handler. A non-word data handler
+ * is responsible for converting words from a Modbus packet into the
+ * non-word values associated with the actual device's registers.
+ *
+ * @param dhandler a NonWordDataHandler instance.
+ */
+ public void setNonWordDataHandler(NonWordDataHandler dhandler) {
+ nonWordDataHandler = dhandler;
+ }
+
+ public void writeData(DataOutput output) throws IOException {
+ output.write(getMessage());
+ }
+
+ public void readData(DataInput input) throws IOException {
+ reference = input.readUnsignedShort();
+ int registerCount = input.readUnsignedShort();
+ int byteCount = input.readUnsignedByte();
+
+ if (nonWordDataHandler == null) {
+ byte buffer[] = new byte[byteCount];
+ input.readFully(buffer, 0, byteCount);
+
+ int offset = 0;
+ registers = new Register[registerCount];
+
+ for (int register = 0; register < registerCount; register++) {
+ registers[register] = new SimpleRegister(buffer[offset], buffer[offset + 1]);
+ offset += 2;
+ }
+ }
+ else {
+ nonWordDataHandler.readData(input, reference, registerCount);
+ }
+ }
+
+ public byte[] getMessage() {
+ int len = 5;
+
+ if (registers != null) {
+ len += registers.length * 2;
+ }
+
+ byte result[] = new byte[len];
+ int registerCount = registers != null ? registers.length : 0;
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((registerCount >> 8) & 0xff);
+ result[3] = (byte)(registerCount & 0xff);
+ result[4] = (byte)(registerCount * 2);
+
+ int offset = 5;
+
+ if (nonWordDataHandler == null) {
+ for (int i = 0; i < registerCount; i++) {
+ byte bytes[] = registers[i].toBytes();
+ result[offset++] = bytes[0];
+ result[offset++] = bytes[1];
+ }
+ }
+ else {
+ nonWordDataHandler.prepareData(reference, registerCount);
+ byte bytes[] = nonWordDataHandler.getData();
+ if (bytes != null) {
+ int nonWordBytes = bytes.length;
+ if (nonWordBytes > registerCount * 2) {
+ nonWordBytes = registerCount * 2;
+ }
+
+ System.arraycopy(bytes, 0, result, offset, nonWordBytes);
+ }
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersResponse.java
new file mode 100644
index 0000000..846585f
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteMultipleRegistersResponse.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteMultipleRegistersResponse. The
+ * implementation directly correlates with the class 0 function read multiple
+ * registers (FC 16). It encapsulates the corresponding response message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteMultipleRegistersResponse extends ModbusResponse {
+ // instance attributes
+ private int wordCount;
+ private int reference;
+
+ /**
+ * Constructs a new WriteMultipleRegistersResponse instance.
+ */
+ public WriteMultipleRegistersResponse() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new WriteMultipleRegistersResponse instance.
+ *
+ * @param reference the offset to start reading from.
+ * @param wordCount the number of words (registers) to be read.
+ */
+ public WriteMultipleRegistersResponse(int reference, int wordCount) {
+ super();
+
+ setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
+ setDataLength(4);
+
+ this.reference = reference;
+ this.wordCount = wordCount;
+ }
+
+ /**
+ * Returns the reference of the register to start writing to with this
+ * WriteMultipleRegistersResponse.
+ *
+ *
+ * @return the reference of the register to start writing to as int
+ * .
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to start writing to with this
+ * WriteMultipleRegistersResponse.
+ *
+ *
+ * @param ref the reference of the register to start writing to as
+ * int.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the number of bytes that have been written.
+ *
+ * @return the number of bytes that have been read as int.
+ */
+ public int getByteCount() {
+ return wordCount * 2;
+ }
+
+ /**
+ * Returns the number of words that have been read. The returned value
+ * should be half of the byte count of the response.
+ *
+ *
+ * @return the number of words that have been read as int.
+ */
+ public int getWordCount() {
+ return wordCount;
+ }
+
+ /**
+ * Sets the number of words that have been returned.
+ *
+ * @param count the number of words as int.
+ */
+ public void setWordCount(int count) {
+ wordCount = count;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ setReference(din.readUnsignedShort());
+ setWordCount(din.readUnsignedShort());
+
+ setDataLength(4);
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((wordCount >> 8) & 0xff);
+ result[3] = (byte)(wordCount & 0xff);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterRequest.java
new file mode 100644
index 0000000..f8036cf
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterRequest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.procimg.IllegalAddressException;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.procimg.Register;
+import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteSingleRegisterRequest. The implementation
+ * directly correlates with the class 0 function write single register (FC
+ * 6). It encapsulates the corresponding request message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteSingleRegisterRequest extends ModbusRequest {
+
+ // instance attributes
+ private int reference;
+ private Register register;
+
+ /**
+ * Constructs a new WriteSingleRegisterRequest instance.
+ */
+ public WriteSingleRegisterRequest() {
+ super();
+
+ setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
+ setDataLength(4);
+ }
+
+ /**
+ * Constructs a new WriteSingleRegisterRequest instance with a
+ * given reference and value to be written.
+ *
+ * @param ref the reference number of the register to read from.
+ * @param reg the register containing the data to be written.
+ */
+ public WriteSingleRegisterRequest(int ref, Register reg) {
+ super();
+
+ setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
+ setDataLength(4);
+
+ reference = ref;
+ register = reg;
+ }
+
+ @Override
+ public ModbusResponse getResponse() {
+ return updateResponseWithHeader(new WriteSingleRegisterResponse());
+ }
+
+ @Override
+ public ModbusResponse createResponse(AbstractModbusListener listener) {
+ Register reg;
+
+ // 1. get process image
+ ProcessImage procimg = listener.getProcessImage(getUnitID());
+
+ // 2. get register
+ try {
+ reg = procimg.getRegister(reference);
+
+ // 3. set Register
+ reg.setValue(register.toBytes());
+ }
+ catch (IllegalAddressException iaex) {
+ return createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ }
+ return updateResponseWithHeader(new WriteSingleRegisterResponse(this.getReference(), reg.getValue()));
+ }
+
+ /**
+ * Returns the reference of the register to be written to with this
+ * WriteSingleRegisterRequest.
+ *
+ * @return the reference of the register to be written to.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register to be written to with this
+ * WriteSingleRegisterRequest.
+ *
+ * @param ref the reference of the register to be written to.
+ */
+ public void setReference(int ref) {
+ reference = ref;
+ }
+
+ /**
+ * Returns the register to be written with this
+ * WriteSingleRegisterRequest.
+ *
+ * @return the value to be written to the register.
+ */
+ public Register getRegister() {
+ return register;
+ }
+
+ /**
+ * Sets the value that should be written to the register with this
+ * WriteSingleRegisterRequest.
+ *
+ * @param reg the register to be written.
+ */
+ public void setRegister(Register reg) {
+ register = reg;
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.writeShort(reference);
+ dout.write(register.toBytes());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ reference = din.readUnsignedShort();
+ register = new SimpleRegister(din.readByte(), din.readByte());
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((register.getValue() >> 8) & 0xff);
+ result[3] = (byte)(register.getValue() & 0xff);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterResponse.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterResponse.java
new file mode 100644
index 0000000..e5fee12
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/WriteSingleRegisterResponse.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Class implementing a WriteSingleRegisterResponse.
+ * The implementation directly correlates with the class 0
+ * function write single register (FC 6). It
+ * encapsulates the corresponding response message.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class WriteSingleRegisterResponse
+ extends ModbusResponse {
+
+ //instance attributes
+ private int reference;
+ private int registerValue;
+
+ /**
+ * Constructs a new WriteSingleRegisterResponse
+ * instance.
+ */
+ public WriteSingleRegisterResponse() {
+ super();
+ setDataLength(4);
+ setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
+ }
+
+ /**
+ * Constructs a new WriteSingleRegisterResponse
+ * instance.
+ *
+ * @param reference the offset of the register written.
+ * @param value the value of the register.
+ */
+ public WriteSingleRegisterResponse(int reference, int value) {
+ super();
+ setReference(reference);
+ setRegisterValue(value);
+ setDataLength(4);
+ setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
+ }
+
+ /**
+ * Returns the value that has been returned in
+ * this WriteSingleRegisterResponse.
+ *
+ *
+ * @return the value of the register.
+ */
+ public int getRegisterValue() {
+ return registerValue;
+ }
+
+ /**
+ * Sets the value that has been returned in the
+ * response message.
+ *
+ *
+ * @param value the returned register value.
+ */
+ private void setRegisterValue(int value) {
+ registerValue = value;
+ }
+
+ /**
+ * Returns the reference of the register
+ * that has been written to.
+ *
+ *
+ * @return the reference of the written register.
+ */
+ public int getReference() {
+ return reference;
+ }
+
+ /**
+ * Sets the reference of the register that has
+ * been written to.
+ *
+ *
+ * @param ref the reference of the written register.
+ */
+ private void setReference(int ref) {
+ reference = ref;
+ //setChanged(true);
+ }
+
+ public void writeData(DataOutput dout) throws IOException {
+ dout.write(getMessage());
+ }
+
+ public void readData(DataInput din) throws IOException {
+ setReference(din.readUnsignedShort());
+ setRegisterValue(din.readUnsignedShort());
+ //update data length
+ setDataLength(4);
+ }
+
+ public byte[] getMessage() {
+ byte result[] = new byte[4];
+
+ result[0] = (byte)((reference >> 8) & 0xff);
+ result[1] = (byte)(reference & 0xff);
+ result[2] = (byte)((registerValue >> 8) & 0xff);
+ result[3] = (byte)(registerValue & 0xff);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java
new file mode 100644
index 0000000..7334ed0
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractModbusListener.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.slave.ModbusSlave;
+import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+
+/**
+ * Definition of a listener class
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class AbstractModbusListener implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(AbstractModbusListener.class);
+ protected int port = Modbus.DEFAULT_PORT;
+ protected boolean listening;
+ protected InetAddress address;
+ protected String error;
+ protected int timeout = Modbus.DEFAULT_TIMEOUT;
+
+ /**
+ * Main execution loop for this Modbus interface listener - this is called by
+ * starting the main listening thread
+ */
+ public abstract void run();
+
+ /**
+ * Stop the listener thread for this ModbusListener instance.
+ */
+ public abstract void stop();
+
+ /**
+ * Sets the port to be listened to.
+ *
+ * @param port the number of the IP port as int.
+ */
+ public void setPort(int port) {
+ this.port = ((port > 0) ? port : Modbus.DEFAULT_PORT);
+ }
+
+ /**
+ * Returns the port being listened on
+ *
+ * @return Port number > 0
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Sets the address of the interface to be listened to.
+ *
+ * @param addr an InetAddress instance.
+ */
+ public void setAddress(InetAddress addr) {
+ address = addr;
+ }
+
+ /**
+ * Returns the address bound to this socket
+ *
+ * @return Bound address
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Tests if this ModbusTCPListener is listening and accepting
+ * incoming connections.
+ *
+ * @return true if listening (and accepting incoming connections), false
+ * otherwise.
+ */
+ public boolean isListening() {
+ return listening;
+ }
+
+ /**
+ * Set the listening state of this ModbusTCPListener object.
+ * A ModbusTCPListener will silently drop any requests if the
+ * listening state is set to false.
+ *
+ * @param b listening state
+ */
+ public void setListening(boolean b) {
+ listening = b;
+ }
+
+ /**
+ * Returns any startup errors that may have aoccurred
+ *
+ * @return Error string
+ */
+ public String getError() {
+ return error;
+ }
+
+ /**
+ * Get the socket timeout
+ *
+ * @return Socket timeout in milliseconds
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the socket timeout
+ *
+ * @param timeout Timeout in milliseconds
+ */
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ /**
+ * Reads the request, checks it is valid and that the unit ID is ok
+ * and sends back a response
+ *
+ * @param transport Transport to read request from
+ * @param listener Listener that the request was received by
+ * @throws ModbusIOException If there is an issue with the transport or transmission
+ */
+ void handleRequest(AbstractModbusTransport transport, AbstractModbusListener listener) throws ModbusIOException {
+
+ // Get the request from the transport. It will be processed
+ // using an associated process image
+
+ if (transport == null) {
+ throw new ModbusIOException("No transport specified");
+ }
+ ModbusRequest request = transport.readRequest(listener);
+ if (request == null) {
+ throw new ModbusIOException("Request for transport %s is invalid (null)", transport.getClass().getSimpleName());
+ }
+ ModbusResponse response;
+
+ // Test if Process image exists for this Unit ID
+ ProcessImage spi = getProcessImage(request.getUnitID());
+ if (spi == null) {
+ response = request.createExceptionResponse(Modbus.ILLEGAL_ADDRESS_EXCEPTION);
+ response.setAuxiliaryType(ModbusResponse.AuxiliaryMessageTypes.UNIT_ID_MISSMATCH);
+ }
+ else {
+ response = request.createResponse(this);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Request:{}", request.getHexMessage());
+ logger.debug("Response:{}", response.getHexMessage());
+ }
+
+ // Write the response
+ transport.writeResponse(response);
+ }
+
+ /**
+ * Returns the related process image for this listener and Unit Id
+ *
+ * @param unitId Unit ID
+ * @return Process image associated with this listener and Unit ID
+ */
+ public ProcessImage getProcessImage(int unitId) {
+ ModbusSlave slave = ModbusSlaveFactory.getSlave(this);
+ if (slave != null) {
+ return slave.getProcessImage(unitId);
+ }
+ return null;
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractSerialConnection.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractSerialConnection.java
new file mode 100644
index 0000000..60346ea
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractSerialConnection.java
@@ -0,0 +1,190 @@
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Interface that represents a public abstract serial port connection
+ *
+ * @author Felipe Herranz
+ * @version 2.0 (March 2016)
+ */
+public abstract class AbstractSerialConnection {
+
+ /**
+ * Parity values
+ */
+ public static final int NO_PARITY = 0;
+ public static final int ODD_PARITY = 1;
+ public static final int EVEN_PARITY = 2;
+ public static final int MARK_PARITY = 3;
+ public static final int SPACE_PARITY = 4;
+
+ /**
+ * Stop bits values
+ */
+ public static final int ONE_STOP_BIT = 1;
+ public static final int ONE_POINT_FIVE_STOP_BITS = 2;
+ public static final int TWO_STOP_BITS = 3;
+
+ /**
+ * Flow control values
+ */
+ public static final int FLOW_CONTROL_DISABLED = 0;
+ public static final int FLOW_CONTROL_RTS_ENABLED = 1;
+ public static final int FLOW_CONTROL_CTS_ENABLED = 16;
+ public static final int FLOW_CONTROL_DSR_ENABLED = 256;
+ public static final int FLOW_CONTROL_DTR_ENABLED = 4096;
+ public static final int FLOW_CONTROL_XONXOFF_IN_ENABLED = 65536;
+ public static final int FLOW_CONTROL_XONXOFF_OUT_ENABLED = 1048576;
+
+ /**
+ * Open delay (msec)
+ */
+ public static final int OPEN_DELAY = 0;
+
+ /**
+ * Timeout
+ */
+ static final public int TIMEOUT_NONBLOCKING = 0x00000000;
+ static final public int TIMEOUT_READ_SEMI_BLOCKING = 0x00000001;
+ static final public int TIMEOUT_WRITE_SEMI_BLOCKING = 0x00000010;
+ static final public int TIMEOUT_READ_BLOCKING = 0x00000100;
+ static final public int TIMEOUT_WRITE_BLOCKING = 0x00001000;
+ static final public int TIMEOUT_SCANNER = 0x00010000;
+
+ /**
+ * Returns the ModbusTransport instance to be used for receiving
+ * and sending messages.
+ *
+ * @throws IOException If the port is not available or cannot be opened
+ */
+ public abstract void open() throws IOException;
+
+ /**
+ * Returns the ModbusTransport instance to be used for receiving
+ * and sending messages.
+ *
+ * @return a ModbusTransport instance.
+ */
+ public abstract AbstractModbusTransport getModbusTransport();
+
+ /**
+ * Read a specified number of bytes from the serial port
+ *
+ * @param buffer Buffer to recieve bytes from the port
+ * @param bytesToRead Number of bytes to read
+ * @return number of currently bytes read.
+ */
+ public abstract int readBytes(byte[] buffer, long bytesToRead);
+
+ /**
+ * Write a specified number of bytes to the serial port
+ *
+ * @param buffer Bytes to send to the port
+ * @param bytesToWrite How many bytes to send
+ * @return number of currently bytes written
+ */
+ public abstract int writeBytes(byte[] buffer, long bytesToWrite);
+
+ /**
+ * Bytes available to read
+ *
+ * @return number of bytes currently available to read
+ */
+ public abstract int bytesAvailable();
+
+ /**
+ * Sets the connection parameters to the setting in the parameters object.
+ * If set fails return the parameters object to original settings and throw
+ * exception.
+ */
+ public abstract void setConnectionParameters();
+
+ /**
+ * Close the port and clean up associated elements.
+ */
+ public abstract void close();
+
+ /**
+ * Returns current baud rate
+ *
+ * @return Baud rate
+ */
+ public abstract int getBaudRate();
+
+ /**
+ * Set new baud rate
+ *
+ * @param newBaudRate Baud rate
+ */
+ public abstract void setBaudRate(int newBaudRate);
+
+ /**
+ * Returns current data bits value
+ *
+ * @return Number of data bits
+ */
+ public abstract int getNumDataBits();
+
+ /**
+ * Returns current stop bits
+ *
+ * @return Number of stop bits
+ */
+ public abstract int getNumStopBits();
+
+ /**
+ * Returns current parity
+ *
+ * @return Parity type
+ */
+ public abstract int getParity();
+
+ /**
+ * Returns a descriptive name of the current port
+ *
+ * @return a String instance.
+ */
+ public abstract String getDescriptivePortName();
+
+ /**
+ * Set port timeouts
+ *
+ * @param newTimeoutMode Timeout mode
+ * @param newReadTimeout Read timeout
+ * @param newWriteTimeout Write timeout
+ */
+ public abstract void setComPortTimeouts(int newTimeoutMode, int newReadTimeout, int newWriteTimeout);
+
+ /**
+ * Reports the open status of the port.
+ *
+ * @return true if port is open, false if port is closed.
+ */
+ public abstract boolean isOpen();
+
+ /**
+ * Returns the timeout for this UDPMasterConnection.
+ *
+ * @return the timeout as int.
+ */
+ public abstract int getTimeout();
+
+ /**
+ * Sets the timeout for this UDPMasterConnection.
+ *
+ * @param timeout the timeout as int.
+ */
+ public abstract void setTimeout(int timeout);
+
+ /**
+ * Returns a set of all the available comm port names
+ *
+ * @return Set of comm port names
+ */
+ public abstract Set
+ * If listening, it accepts incoming requests passing them on to be handled.
+ * If not listening, silently drops the requests.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusTCPListener extends AbstractModbusListener {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusTCPListener.class);
+
+ private ServerSocket serverSocket = null;
+ private ThreadPool threadPool;
+ private Thread listener;
+ private boolean useRtuOverTcp;
+
+ /**
+ * Constructs a ModbusTCPListener instance.
+ * The connections will be handling using the ModbusCouple class
+ * and a ProcessImage which provides the interface between the
+ * slave implementation and the TCPSlaveConnection.
+ *
+ * @param listener the listener that handled the incoming request
+ * @param connection an incoming connection.
+ */
+ public TCPConnectionHandler(AbstractModbusListener listener, TCPSlaveConnection connection) {
+ this.listener = listener;
+ this.connection = connection;
+ transport = this.connection.getModbusTransport();
+ }
+
+ @Override
+ public void run() {
+ try {
+ do {
+ listener.handleRequest(transport, listener);
+ } while (!Thread.currentThread().isInterrupted());
+ }
+ catch (ModbusIOException ex) {
+ if (!ex.isEOF()) {
+ logger.debug(ex.getMessage());
+ }
+ }
+ finally {
+ connection.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPMasterConnection.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPMasterConnection.java
new file mode 100644
index 0000000..07a8885
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPMasterConnection.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.io.ModbusRTUTCPTransport;
+import com.ghgande.j2mod.modbus.io.ModbusTCPTransport;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * Class that implements a TCPMasterConnection.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class TCPMasterConnection {
+
+ private static final Logger logger = LoggerFactory.getLogger(TCPMasterConnection.class);
+
+ // instance attributes
+ private Socket socket;
+ private int timeout = Modbus.DEFAULT_TIMEOUT;
+ private boolean connected;
+
+ private InetAddress address;
+ private int port = Modbus.DEFAULT_PORT;
+
+ private ModbusTCPTransport transport;
+
+ private boolean useRtuOverTcp = false;
+
+ /**
+ * useUrgentData - sent a byte of urgent data when testing the TCP
+ * connection.
+ */
+ private boolean useUrgentData = false;
+
+ /**
+ * Constructs a TCPMasterConnection instance with a given
+ * destination address.
+ *
+ * @param adr the destination InetAddress.
+ */
+ public TCPMasterConnection(InetAddress adr) {
+ address = adr;
+ }
+
+ /**
+ * Prepares the associated ModbusTransport of this
+ * TCPMasterConnection for use.
+ *
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ *
+ * @throws IOException if an I/O related error occurs.
+ */
+ private void prepareTransport(boolean useRtuOverTcp) throws IOException {
+
+ // If we don't have a transport, or the transport type has changed
+ if (transport == null || (this.useRtuOverTcp != useRtuOverTcp)) {
+
+ // Save the flag to tell us which transport type to use
+ this.useRtuOverTcp = useRtuOverTcp;
+
+ // Select the correct transport
+ if (useRtuOverTcp) {
+ logger.trace("prepareTransport() -> using RTU over TCP transport.");
+ transport = new ModbusRTUTCPTransport(socket);
+ transport.setMaster(this);
+ }
+ else {
+ logger.trace("prepareTransport() -> using standard TCP transport.");
+ transport = new ModbusTCPTransport(socket);
+ transport.setMaster(this);
+ }
+ }
+ else {
+ logger.trace("prepareTransport() -> using custom transport: {}", transport.getClass().getSimpleName());
+ transport.setSocket(socket);
+ }
+ transport.setTimeout(timeout);
+ }
+
+ /**
+ * Opens this TCPMasterConnection.
+ *
+ * @throws Exception if there is a network failure.
+ */
+ public synchronized void connect() throws Exception {
+ connect(useRtuOverTcp);
+ }
+
+ /**
+ * Opens this TCPMasterConnection.
+ *
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ *
+ * @throws Exception if there is a network failure.
+ */
+ public synchronized void connect(boolean useRtuOverTcp) throws Exception {
+ if (!isConnected()) {
+ logger.debug("connect()");
+
+ // Create a socket without auto-connecting
+
+ socket = new Socket();
+ socket.setReuseAddress(true);
+ socket.setSoLinger(true, 1);
+ socket.setKeepAlive(true);
+ setTimeout(timeout);
+
+ // Connect - only wait for the timeout number of milliseconds
+
+ socket.connect(new InetSocketAddress(address, port), timeout);
+
+ // Prepare the transport
+
+ prepareTransport(useRtuOverTcp);
+ connected = true;
+ }
+ }
+
+ /**
+ * Tests if this TCPMasterConnection is connected.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public synchronized boolean isConnected() {
+ if (connected && socket != null) {
+ if (!socket.isConnected() || socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
+ try {
+ socket.close();
+ }
+ catch (IOException e) {
+ logger.error("Socket exception", e);
+ }
+ finally {
+ connected = false;
+ }
+ }
+ else {
+ /*
+ * When useUrgentData is set, a byte of urgent data
+ * will be sent to the server to test the connection. If
+ * the connection is actually broken, an IException will
+ * occur and the connection will be closed.
+ *
+ * Note: RFC 6093 has decreed that we stop using urgent
+ * data.
+ */
+ if (useUrgentData) {
+ try {
+ socket.sendUrgentData(0);
+ ModbusUtil.sleep(5);
+ }
+ catch (IOException e) {
+ connected = false;
+ try {
+ socket.close();
+ }
+ catch (IOException e1) {
+ // Do nothing.
+ }
+ }
+ }
+ }
+ }
+ return connected;
+ }
+
+ /**
+ * Closes this TCPMasterConnection.
+ */
+ public void close() {
+ if (connected) {
+ try {
+ transport.close();
+ }
+ catch (IOException ex) {
+ logger.debug("close()", ex);
+ }
+ finally {
+ connected = false;
+ }
+ }
+ }
+
+ /**
+ * Returns the ModbusTransport associated with this
+ * TCPMasterConnection.
+ *
+ * @return the connection's ModbusTransport.
+ */
+ public AbstractModbusTransport getModbusTransport() {
+ return transport;
+ }
+
+ /**
+ * Set the ModbusTransport associated with this
+ * TCPMasterConnection
+ * @param trans associated transport
+ */
+ public void setModbusTransport(ModbusTCPTransport trans) {
+ transport = trans;
+ }
+
+ /**
+ * Returns the timeout (msec) for this TCPMasterConnection.
+ *
+ * @return the timeout as int.
+ */
+ public synchronized int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the timeout (msec) for this TCPMasterConnection. This is both the
+ * connection timeout and the transaction timeout
+ *
+ * @param timeout - the timeout in milliseconds as an int.
+ */
+ public synchronized void setTimeout(int timeout) {
+ try {
+ this.timeout = timeout;
+ if (socket != null) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+ catch (IOException ex) {
+ logger.warn("Could not set timeout to value " + timeout, ex);
+ }
+ }
+
+ /**
+ * Returns the destination port of this TCPMasterConnection.
+ *
+ * @return the port number as int.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Sets the destination port of this TCPMasterConnection. The
+ * default is defined as Modbus.DEFAULT_PORT.
+ *
+ * @param port the port number as int.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * Returns the destination InetAddress of this
+ * TCPMasterConnection.
+ *
+ * @return the destination address as InetAddress.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Sets the destination InetAddress of this
+ * TCPMasterConnection.
+ *
+ * @param adr the destination address as InetAddress.
+ */
+ public void setAddress(InetAddress adr) {
+ address = adr;
+ }
+
+ /**
+ * Gets the current setting of the flag which controls sending
+ * urgent data to test a network connection.
+ *
+ * @return Status
+ */
+ public boolean getUseUrgentData() {
+ return useUrgentData;
+ }
+
+ /**
+ * Set the flag which controls sending urgent data to test a
+ * network connection.
+ *
+ * @param useUrgentData - Connections are testing using urgent data.
+ */
+ public void setUseUrgentData(boolean useUrgentData) {
+ this.useUrgentData = useUrgentData;
+ }
+
+ /**
+ * Returns true if this connection is an RTU over TCP type
+ *
+ * @return True if RTU over TCP
+ */
+ public boolean isUseRtuOverTcp() {
+ return useRtuOverTcp;
+ }
+
+ /**
+ * Sets the transport type to use
+ * Normally set during the connection but can also be set after a connection has been established
+ *
+ * @param useRtuOverTcp True if the transport should be interpreted as RTU over tCP
+ *
+ * @throws Exception If the connection is not valid
+ */
+ public void setUseRtuOverTcp(boolean useRtuOverTcp) throws Exception {
+ this.useRtuOverTcp = useRtuOverTcp;
+ if (isConnected()) {
+ prepareTransport(useRtuOverTcp);
+ }
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPSlaveConnection.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPSlaveConnection.java
new file mode 100644
index 0000000..654d9be
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPSlaveConnection.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import com.ghgande.j2mod.modbus.io.ModbusRTUTCPTransport;
+import com.ghgande.j2mod.modbus.io.ModbusTCPTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * Class that implements a TCPSlaveConnection.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class TCPSlaveConnection {
+
+ private static final Logger logger = LoggerFactory.getLogger(TCPSlaveConnection.class);
+
+ // instance attributes
+ private Socket socket;
+ private int timeout = Modbus.DEFAULT_TIMEOUT;
+ private boolean connected;
+ private ModbusTCPTransport transport;
+
+ /**
+ * Constructs a TCPSlaveConnection instance using a given socket
+ * instance.
+ *
+ * @param socket the socket instance to be used for communication.
+ */
+ public TCPSlaveConnection(Socket socket) {
+ this(socket, false);
+ }
+
+ /**
+ * Constructs a TCPSlaveConnection instance using a given socket
+ * instance.
+ *
+ * @param socket the socket instance to be used for communication.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public TCPSlaveConnection(Socket socket, boolean useRtuOverTcp) {
+ try {
+ setSocket(socket, useRtuOverTcp);
+ }
+ catch (IOException ex) {
+ logger.debug("TCPSlaveConnection::Socket invalid");
+
+ throw new IllegalStateException("Socket invalid", ex);
+ }
+ }
+
+ /**
+ * Closes this TCPSlaveConnection.
+ */
+ public void close() {
+ if (connected) {
+ try {
+ transport.close();
+ socket.close();
+ }
+ catch (IOException ex) {
+ logger.warn("Could not close socket", ex);
+ }
+ connected = false;
+ }
+ }
+
+ /**
+ * Returns the ModbusTransport associated with this
+ * TCPMasterConnection.
+ *
+ * @return the connection's ModbusTransport.
+ */
+ public AbstractModbusTransport getModbusTransport() {
+ return transport;
+ }
+
+ /**
+ * Prepares the associated ModbusTransport of this
+ * TCPMasterConnection for use.
+ *
+ * @param socket the socket to be used for communication.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ * @throws IOException if an I/O related error occurs.
+ */
+ private void setSocket(Socket socket, boolean useRtuOverTcp) throws IOException {
+ this.socket = socket;
+
+ if (transport == null) {
+ if (useRtuOverTcp) {
+ logger.trace("setSocket() -> using RTU over TCP transport.");
+ transport = new ModbusRTUTCPTransport(socket);
+ }
+ else {
+ logger.trace("setSocket() -> using standard TCP transport.");
+ transport = new ModbusTCPTransport(socket);
+ }
+ }
+ else {
+ transport.setSocket(socket);
+ }
+
+ connected = true;
+ }
+
+ /**
+ * Returns the timeout for this TCPSlaveConnection.
+ *
+ * @return the timeout as int.
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the timeout for this TCPSlaveConnection.
+ *
+ * @param timeout the timeout in milliseconds as int.
+ */
+ public void setTimeout(int timeout) {
+ this.timeout = timeout;
+
+ try {
+ socket.setSoTimeout(timeout);
+ }
+ catch (IOException ex) {
+ logger.warn("Could not set timeout to " + timeout, ex);
+ }
+ }
+
+ /**
+ * Returns the destination port of this TCPSlaveConnection.
+ *
+ * @return the port number as int.
+ */
+ public int getPort() {
+ return socket.getLocalPort();
+ }
+
+ /**
+ * Returns the destination InetAddress of this
+ * TCPSlaveConnection.
+ *
+ * @return the destination address as InetAddress.
+ */
+ public InetAddress getAddress() {
+ return socket.getLocalAddress();
+ }
+
+ /**
+ * Tests if this TCPSlaveConnection is connected.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterConnection.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterConnection.java
new file mode 100644
index 0000000..04357db
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterConnection.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+
+/**
+ * Class that implements a UDPMasterConnection.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class UDPMasterConnection {
+
+ private static final Logger logger = LoggerFactory.getLogger(UDPMasterConnection.class);
+
+ //instance attributes
+ private UDPMasterTerminal terminal;
+ private int timeout = Modbus.DEFAULT_TIMEOUT;
+ private boolean connected;
+
+ private InetAddress address;
+ private int port = Modbus.DEFAULT_PORT;
+
+ /**
+ * Constructs a UDPMasterConnection instance
+ * with a given destination address.
+ *
+ * @param adr the destination InetAddress.
+ */
+ public UDPMasterConnection(InetAddress adr) {
+ address = adr;
+ }
+
+ /**
+ * Opens this UDPMasterConnection.
+ *
+ * @throws Exception if there is a network failure.
+ */
+ public synchronized void connect() throws Exception {
+ if (!connected) {
+ terminal = new UDPMasterTerminal(address);
+ terminal.setPort(port);
+ terminal.setTimeout(timeout);
+ terminal.activate();
+ connected = true;
+ }
+ }
+
+ /**
+ * Closes this UDPMasterConnection.
+ */
+ public void close() {
+ if (connected) {
+ try {
+ terminal.deactivate();
+ }
+ catch (Exception ex) {
+ logger.debug("Exception occurred while closing UDPMasterConnection", ex);
+ }
+ connected = false;
+ }
+ }
+
+ /**
+ * Returns the ModbusTransport associated with this
+ * UDPMasterConnection.
+ *
+ * @return the connection's ModbusTransport.
+ */
+ public AbstractModbusTransport getModbusTransport() {
+ return terminal == null ? null : terminal.getTransport();
+ }
+
+ /**
+ * Returns the terminal used for handling the package traffic.
+ *
+ * @return a UDPTerminal instance.
+ */
+ public AbstractUDPTerminal getTerminal() {
+ return terminal;
+ }
+
+ /**
+ * Returns the timeout for this UDPMasterConnection.
+ *
+ * @return the timeout as int.
+ */
+ public synchronized int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Sets the timeout for this UDPMasterConnection.
+ *
+ * @param timeout the timeout as int.
+ */
+ public synchronized void setTimeout(int timeout) {
+ this.timeout = timeout;
+ if (terminal != null) {
+ terminal.setTimeout(timeout);
+ }
+ }
+
+ /**
+ * Returns the destination port of this
+ * UDPMasterConnection.
+ *
+ * @return the port number as int.
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Sets the destination port of this
+ * UDPMasterConnection.
+ * The default is defined as Modbus.DEFAULT_PORT.
+ *
+ * @param port the port number as int.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * Returns the destination InetAddress of this
+ * UDPMasterConnection.
+ *
+ * @return the destination address as InetAddress.
+ */
+ public InetAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Sets the destination InetAddress of this
+ * UDPMasterConnection.
+ *
+ * @param adr the destination address as InetAddress.
+ */
+ public void setAddress(InetAddress adr) {
+ address = adr;
+ }
+
+ /**
+ * Tests if this UDPMasterConnection is connected.
+ *
+ * @return true if connected, false otherwise.
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterTerminal.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterTerminal.java
new file mode 100644
index 0000000..c765972
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPMasterTerminal.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.io.ModbusUDPTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+/**
+ * Class implementing a UDPMasterTerminal.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+class UDPMasterTerminal extends AbstractUDPTerminal {
+
+ private static final Logger logger = LoggerFactory.getLogger(UDPMasterTerminal.class);
+
+ /**
+ * Create a UDP master connection to the specified Internet address.
+ *
+ * @param addr Remote address to connect to
+ */
+ UDPMasterTerminal(InetAddress addr) {
+ address = addr;
+ }
+
+ /**
+ * Create an uninitialized UDP master connection.
+ */
+ public UDPMasterTerminal() {
+ }
+
+ @Override
+ public synchronized void activate() throws Exception {
+ if (!isActive()) {
+ if (socket == null) {
+ socket = new DatagramSocket();
+ }
+ logger.debug("UDPMasterTerminal::haveSocket():{}", socket.toString());
+ logger.debug("UDPMasterTerminal::raddr=:{}:rport:{}", address.toString(), port);
+
+ socket.setReceiveBufferSize(1024);
+ socket.setSendBufferSize(1024);
+ socket.setSoTimeout(timeout);
+
+ transport = new ModbusUDPTransport(this);
+ active = true;
+ }
+ logger.debug("UDPMasterTerminal::activated");
+ }
+
+ @Override
+ public synchronized void deactivate() {
+ try {
+ logger.debug("UDPMasterTerminal::deactivate()");
+ if (socket != null) {
+ socket.close();
+ }
+ transport = null;
+ active = false;
+ }
+ catch (Exception ex) {
+ logger.error("Error closing socket", ex);
+ }
+ }
+
+ @Override
+ public synchronized void sendMessage(byte[] msg) throws Exception {
+ DatagramPacket req = new DatagramPacket(msg, msg.length, address, port);
+ socket.send(req);
+ }
+
+ @Override
+ public synchronized byte[] receiveMessage() throws Exception {
+
+ // The longest possible DatagramPacket is 256 bytes (Modbus message
+ // limit) plus the 6 byte header.
+ byte[] buffer = new byte[262];
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
+ socket.setSoTimeout(timeout);
+ socket.receive(packet);
+ return buffer;
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPSlaveTerminal.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPSlaveTerminal.java
new file mode 100644
index 0000000..15ff4b0
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/UDPSlaveTerminal.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.io.ModbusUDPTransport;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.util.Hashtable;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Class implementing a UDPSlaveTerminal.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+class UDPSlaveTerminal extends AbstractUDPTerminal {
+
+ private static final Logger logger = LoggerFactory.getLogger(UDPSlaveTerminal.class);
+ protected Hashtable
+ * In Modbus terms this represents an
+ * input discrete, it is read only from
+ * the slave side.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface DigitalIn {
+
+ /**
+ * Tests if this DigitalIn is set.
+ *
+ *
+ * @return true if set, false otherwise.
+ */
+ boolean isSet();
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalOut.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalOut.java
new file mode 100644
index 0000000..e0dcb04
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalOut.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining a digital output.
+ *
+ * In Modbus terms this represents a
+ * coil, which is read-write from slave and
+ * master or device side.
+ *
+ * @return true if set, false otherwise.
+ */
+ boolean isSet();
+
+ /**
+ * Sets the state of this DigitalOut.
+ *
+ *
+ * @param b true if to be set, false otherwise.
+ */
+ void set(boolean b);
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/FIFO.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/FIFO.java
new file mode 100644
index 0000000..d5f1b72
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/FIFO.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Vector;
+
+/**
+ * @author Julie
+ *
+ * FIFO -- an abstraction of a Modbus FIFO, as supported by the
+ * READ FIFO command.
+ *
+ * The FIFO class is only intended to be used for testing purposes and does
+ * not reflect the actual behavior of a FIFO in a real Modbus device. In an
+ * actual Modbus device, the FIFO is mapped within a fixed address.
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class FIFO {
+
+ private static final Logger logger = LoggerFactory.getLogger(FIFO.class);
+
+ private int address;
+ private int registerCount;
+ private Vector
+ * Note that this is a runtime exception, as it is similar to the
+ * IndexOutOfBoundsException
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class IllegalAddressException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new IllegalAddressException.
+ */
+ public IllegalAddressException() {
+ }
+
+ /**
+ * Constructs a new IllegalAddressException with the given message.
+ *
+ * @param message a message as String.
+ */
+ public IllegalAddressException(String message) {
+ super(message);
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/InputRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/InputRegister.java
new file mode 100644
index 0000000..eee394b
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/InputRegister.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining an input register.
+ *
+ * This register is read only from the slave side.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface InputRegister {
+
+ /**
+ * Returns the value of this InputRegister. The value is stored as
+ * int but should be treated like a 16-bit word.
+ *
+ * @return the value as int.
+ */
+ int getValue();
+
+ /**
+ * Returns the content of this Register as unsigned 16-bit value
+ * (unsigned short).
+ *
+ * @return the content as unsigned short (int).
+ */
+ int toUnsignedShort();
+
+ /**
+ * Returns the content of this Register as signed 16-bit value
+ * (short).
+ *
+ * @return the content as short.
+ */
+ short toShort();
+
+ /**
+ * Returns the content of this Register as bytes.
+ *
+ * @return a byte[] with length 2.
+ */
+ byte[] toBytes();
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableDigitalOut.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableDigitalOut.java
new file mode 100644
index 0000000..67b1d66
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableDigitalOut.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+import com.ghgande.j2mod.modbus.util.Observable;
+
+/**
+ * Class implementing an observable digital output.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ObservableDigitalOut extends Observable implements DigitalOut {
+
+ /**
+ * A boolean holding the state of this digital out.
+ */
+ protected boolean set;
+
+ /**
+ * Determine if the digital output is set.
+ *
+ * @return the boolean value of the digital output.
+ */
+ public boolean isSet() {
+ return set;
+ }
+
+ /**
+ * Set or clear the digital output. Will notify any registered
+ * observers.
+ */
+ public void set(boolean b) {
+ set = b;
+ notifyObservers("value");
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableRegister.java
new file mode 100644
index 0000000..1722734
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ObservableRegister.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+import com.ghgande.j2mod.modbus.util.Observable;
+
+/**
+ * Class implementing an observable register.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ObservableRegister extends Observable implements Register {
+
+ /**
+ * The word holding the content of this register.
+ */
+ protected short register;
+
+ synchronized public int getValue() {
+ return register & 0xFFFF;
+ }
+
+ public final int toUnsignedShort() {
+ return register & 0xFFFF;
+ }
+
+ public final short toShort() {
+ return register;
+ }
+
+ public synchronized byte[] toBytes() {
+ return new byte[]{(byte)(register >> 8), (byte)(register & 0xFF)};
+ }
+
+ public final synchronized void setValue(short s) {
+ register = s;
+ notifyObservers("value");
+ }
+
+ public final synchronized void setValue(byte[] bytes) {
+ if (bytes.length < 2) {
+ throw new IllegalArgumentException();
+ }
+ else {
+ register = (short)(((short)((bytes[0] << 8))) | (((short)(bytes[1])) & 0xFF));
+ notifyObservers("value");
+ }
+ }
+
+ public final synchronized void setValue(int v) {
+ register = (short)v;
+ notifyObservers("value");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImage.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImage.java
new file mode 100644
index 0000000..3da266c
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImage.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining a process image in an object oriented manner.
+ *
+ * The process image is understood as a shared memory area used form
+ * communication between slave and master or device side.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface ProcessImage {
+
+ /**
+ * Returns a range of DigitalOut instances.
+ *
+ * @param offset the start offset.
+ * @param count the amount of DigitalOut from the offset.
+ *
+ * @return an array of DigitalOut instances.
+ *
+ * @throws IllegalAddressException if the range from offset to offset+count is non existant.
+ */
+ DigitalOut[] getDigitalOutRange(int offset, int count) throws IllegalAddressException;
+
+ /**
+ * Returns the DigitalOut instance at the given reference.
+ *
+ * @param ref the reference.
+ *
+ * @return the DigitalOut instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ DigitalOut getDigitalOut(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of DigitalOut instances in this
+ * ProcessImage.
+ *
+ * @return the number of digital outs as int.
+ */
+ int getDigitalOutCount();
+
+ /**
+ * Returns a range of DigitalIn instances.
+ *
+ * @param offset the start offset.
+ * @param count the amount of DigitalIn from the offset.
+ *
+ * @return an array of DigitalIn instances.
+ *
+ * @throws IllegalAddressException if the range from offset to offset+count is non existant.
+ */
+ DigitalIn[] getDigitalInRange(int offset, int count) throws IllegalAddressException;
+
+ /**
+ * Returns the DigitalIn instance at the given reference.
+ *
+ * @param ref the reference.
+ *
+ * @return the DigitalIn instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ DigitalIn getDigitalIn(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of DigitalIn instances in this
+ * ProcessImage.
+ *
+ * @return the number of digital ins as int.
+ */
+ int getDigitalInCount();
+
+ /**
+ * Returns a range of InputRegister instances.
+ *
+ * @param offset the start offset.
+ * @param count the amount of InputRegister from the offset.
+ *
+ * @return an array of InputRegister instances.
+ *
+ * @throws IllegalAddressException if the range from offset to offset+count is non existant.
+ */
+ InputRegister[] getInputRegisterRange(int offset, int count) throws IllegalAddressException;
+
+ /**
+ * Returns the InputRegister instance at the given reference.
+ *
+ * @param ref the reference.
+ *
+ * @return the InputRegister instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ InputRegister getInputRegister(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of InputRegister instances in this
+ * ProcessImage.
+ *
+ *
+ * This is not the same as the value of the highest addressable register.
+ *
+ * @return the number of input registers as int.
+ */
+ int getInputRegisterCount();
+
+ /**
+ * Returns a range of Register instances.
+ *
+ * @param offset the start offset.
+ * @param count the amount of Register from the offset.
+ *
+ * @return an array of Register instances.
+ *
+ * @throws IllegalAddressException if the range from offset to offset+count is non existant.
+ */
+ Register[] getRegisterRange(int offset, int count) throws IllegalAddressException;
+
+ /**
+ * Returns the Register instance at the given reference.
+ *
+ *
+ * @param ref the reference.
+ *
+ * @return the Register instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ Register getRegister(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of Register instances in this
+ * ProcessImage.
+ *
+ *
+ * This is not the same as the value of the highest addressable register.
+ *
+ * @return the number of registers as int.
+ */
+ int getRegisterCount();
+
+ /**
+ * Returns the File instance at the given reference.
+ *
+ *
+ * @param ref the reference.
+ *
+ * @return the File instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ File getFile(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the File instance having the specified file number.
+ *
+ * @param ref The file number for the File object to be returned.
+ *
+ * @return the File instance having the given number.
+ *
+ * @throws IllegalAddressException if a File with the given number does not exist.
+ */
+ File getFileByNumber(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of File instances in this
+ * ProcessImage.
+ *
+ *
+ * This is not the same as the value of the highest addressable register.
+ *
+ * @return the number of registers as int.
+ */
+ int getFileCount();
+
+ /**
+ * Returns the FIFO instance in the list of all FIFO objects
+ * in this ProcessImage.
+ *
+ * @param ref the reference.
+ *
+ * @return the File instance at the given address.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ FIFO getFIFO(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the FIFO instance having the specified base address.
+ *
+ * @param ref The address for the FIFO object to be returned.
+ *
+ * @return the FIFO instance having the given base address
+ *
+ * @throws IllegalAddressException if a File with the given number does not exist.
+ */
+ FIFO getFIFOByAddress(int ref) throws IllegalAddressException;
+
+ /**
+ * Returns the number of File instances in this
+ * ProcessImage.
+ *
+ *
+ * This is not the same as the value of the highest addressable register.
+ *
+ * @return the number of registers as int.
+ */
+ int getFIFOCount();
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageFactory.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageFactory.java
new file mode 100644
index 0000000..2e1cd80
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageFactory.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining the factory methods for
+ * the process image and it's elements.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface ProcessImageFactory {
+
+ /**
+ * Returns a new ProcessImageImplementation instance.
+ *
+ * @return a ProcessImageImplementation instance.
+ */
+ ProcessImageImplementation createProcessImageImplementation();
+
+ /**
+ * Returns a new DigitalIn instance.
+ *
+ * @return a DigitalIn instance.
+ */
+ DigitalIn createDigitalIn();
+
+ /**
+ * Returns a new DigitalIn instance with the given state.
+ *
+ * @param state true if set, false otherwise.
+ *
+ * @return a DigitalIn instance.
+ */
+ DigitalIn createDigitalIn(boolean state);
+
+ /**
+ * Returns a new DigitalOut instance.
+ *
+ * @return a DigitalOut instance.
+ */
+ DigitalOut createDigitalOut();
+
+ /**
+ * Returns a new DigitalOut instance with the
+ * given state.
+ *
+ * @param b true if set, false otherwise.
+ *
+ * @return a DigitalOut instance.
+ */
+ DigitalOut createDigitalOut(boolean b);
+
+ /**
+ * Returns a new InputRegister instance.
+ *
+ * @return an InputRegister instance.
+ */
+ InputRegister createInputRegister();
+
+ /**
+ * Returns a new InputRegister instance with a
+ * given value.
+ *
+ * @param b1 the first byte.
+ * @param b2 the second byte.
+ *
+ * @return an InputRegister instance.
+ */
+ InputRegister createInputRegister(byte b1, byte b2);
+
+ /**
+ * Creates a new Register instance.
+ *
+ * @return a Register instance.
+ */
+ Register createRegister();
+
+ /**
+ * Returns a new Register instance with a
+ * given value.
+ *
+ * @param b1 the first byte.
+ * @param b2 the second byte.
+ *
+ * @return a Register instance.
+ */
+ Register createRegister(byte b1, byte b2);
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageImplementation.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageImplementation.java
new file mode 100644
index 0000000..b6a5da7
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/ProcessImageImplementation.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining implementation specific details of the
+ * ProcessImage, adding mechanisms for creating and modifying the
+ * actual "process image".
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface ProcessImageImplementation extends ProcessImage {
+
+ /**
+ * Defines the set state (i.e. true) of a digital input or output.
+ */
+ byte DIG_TRUE = 1;
+ /**
+ * Defines the unset state (i.e. false) of a digital input or output.
+ */
+ byte DIG_FALSE = 0;
+ /**
+ * Defines the invalid (unset, neither true nor false) state of a digital
+ * input or output.
+ */
+ byte DIG_INVALID = -1;
+
+ /**
+ * Sets a new DigitalOut instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param out the new DigitalOut instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setDigitalOut(int ref, DigitalOut out) throws IllegalAddressException;
+
+ /**
+ * Adds a new DigitalOut instance.
+ *
+ * @param out the DigitalOut instance to be added.
+ */
+ void addDigitalOut(DigitalOut out);
+
+ /**
+ * Adds a new DigitalOut instance at the given reference.
+ *
+ * @param ref - the reference for the instance.
+ * @param out - the DigitalOut instance to be added.
+ */
+ void addDigitalOut(int ref, DigitalOut out);
+
+ /**
+ * Removes a given DigitalOut instance.
+ *
+ * @param out the DigitalOut instance to be removed.
+ */
+ void removeDigitalOut(DigitalOut out);
+
+ /**
+ * Sets a new DigitalIn instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param di the new DigitalIn instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setDigitalIn(int ref, DigitalIn di) throws IllegalAddressException;
+
+ /**
+ * Adds a new DigitalIn instance.
+ *
+ * @param di the DigitalIn instance to be added.
+ */
+ void addDigitalIn(DigitalIn di);
+
+ /**
+ * Adds a new DigitalIn instance at the given reference, possibly
+ * creating a hole between the last existing reference and the new object.
+ *
+ * @param ref - the reference for the new instance.
+ * @param di the DigitalIn instance to be added.
+ */
+ void addDigitalIn(int ref, DigitalIn di);
+
+ /**
+ * Removes a given DigitalIn instance.
+ *
+ * @param di the DigitalIn instance to be removed.
+ */
+ void removeDigitalIn(DigitalIn di);
+
+ /**
+ * Sets a new InputRegister instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param reg the new InputRegister instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setInputRegister(int ref, InputRegister reg) throws IllegalAddressException;
+
+ /**
+ * Adds a new InputRegister instance.
+ *
+ * @param reg the InputRegister instance to be added.
+ */
+ void addInputRegister(InputRegister reg);
+
+ /**
+ * Adds a new InputRegister instance, possibly
+ * creating a hole between the last existing reference and the new object.
+ *
+ * @param ref - The reference for the new instance.
+ * @param reg the InputRegister instance to be added.
+ */
+ void addInputRegister(int ref, InputRegister reg);
+
+ /**
+ * Removes a given InputRegister instance.
+ *
+ * @param reg the InputRegister instance to be removed.
+ */
+ void removeInputRegister(InputRegister reg);
+
+ /**
+ * Sets a new Register instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param reg the new Register instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setRegister(int ref, Register reg) throws IllegalAddressException;
+
+ /**
+ * Adds a new Register instance.
+ *
+ * @param reg the Register instance to be added.
+ */
+ void addRegister(Register reg);
+
+ /**
+ * Adds a new Register instance, possibly
+ * creating a hole between the last existing reference and the new object.
+ *
+ * @param ref - the reference for the new instance.
+ * @param reg the Register instance to be added.
+ */
+ void addRegister(int ref, Register reg);
+
+ /**
+ * Removes a given Register instance.
+ *
+ * @param reg the Register instance to be removed.
+ */
+ void removeRegister(Register reg);
+
+ /**
+ * Sets a new File instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param reg the new File instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setFile(int ref, File reg) throws IllegalAddressException;
+
+ /**
+ * Adds a new File instance.
+ *
+ * @param reg the File instance to be added.
+ */
+ void addFile(File reg);
+
+ /**
+ * Adds a new File instance, possibly
+ * creating a hole between the last existing reference and the new object.
+ *
+ * @param ref - the reference for the new isntance.
+ * @param reg the File instance to be added.
+ */
+ void addFile(int ref, File reg);
+
+ /**
+ * Removes a given File instance.
+ *
+ * @param reg the File instance to be removed.
+ */
+ void removeFile(File reg);
+
+ /**
+ * Sets a new FIFO instance at the given reference.
+ *
+ * @param ref the reference as int.
+ * @param reg the new FIFO instance to be set.
+ *
+ * @throws IllegalAddressException if the reference is invalid.
+ */
+ void setFIFO(int ref, FIFO reg) throws IllegalAddressException;
+
+ /**
+ * Adds a new FIFO instance.
+ *
+ * @param reg the FIFO instance to be added.
+ */
+ void addFIFO(FIFO reg);
+
+ /**
+ * Adds a new FIFO instance, possibly
+ * creating a hole between the last existing reference and the new object.
+ *
+ * @param ref - the reference for the new instance.
+ * @param reg the FIFO instance to be added.
+ */
+ void addFIFO(int ref, FIFO reg);
+
+ /**
+ * Removes a given FIFO instance.
+ *
+ * @param reg the FIFO instance to be removed.
+ */
+ void removeFIFO(FIFO reg);
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Record.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Record.java
new file mode 100644
index 0000000..924988b
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Record.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Julie
+ *
+ * File -- an abstraction of a Modbus Record, as supported by the
+ * READ FILE RECORD and WRITE FILE RECORD commands.
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class Record {
+
+ private static final Logger logger = LoggerFactory.getLogger(Record.class);
+
+ private int recordNumber;
+ private int registerCount;
+ private Register registers[];
+
+ public Record(int recordNumber, int registers) {
+ this.recordNumber = recordNumber;
+ registerCount = registers;
+ this.registers = new Register[registers];
+
+ for (int i = 0; i < registerCount; i++) {
+ this.registers[i] = new SimpleRegister(0);
+ }
+ }
+
+ public int getRecordNumber() {
+ return recordNumber;
+ }
+
+ public int getRegisterCount() {
+ return registerCount;
+ }
+
+ public Register getRegister(int register) {
+ if (register < 0 || register >= registerCount) {
+ throw new IllegalAddressException();
+ }
+
+ return registers[register];
+ }
+
+ public Record setRegister(int ref, Register register) {
+ if (ref < 0 || ref >= registerCount) {
+ throw new IllegalAddressException();
+ }
+
+ registers[ref] = register;
+
+ return this;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Register.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Register.java
new file mode 100644
index 0000000..31dc153
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/Register.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Interface defining a register.
+ *
+ *
+ * A register is read-write from slave and master or device side. Therefore
+ * implementations have to be carefully designed for concurrency.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface Register extends InputRegister {
+
+ /**
+ * Sets the content of this Register from the given unsigned 16-bit
+ * value (unsigned short).
+ *
+ * @param v the value as unsigned short (int).
+ */
+ void setValue(int v);
+
+ /**
+ * Sets the content of this register from the given signed 16-bit value
+ * (short).
+ *
+ * @param s the value as short.
+ */
+ void setValue(short s);
+
+ /**
+ * Sets the content of this register from the given raw bytes.
+ *
+ * @param bytes the raw data as byte[].
+ */
+ void setValue(byte[] bytes);
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalIn.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalIn.java
new file mode 100644
index 0000000..fdbca84
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalIn.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Class implementing a simple DigitalIn.
+ *
+ * The set method is synchronized, which ensures atomic
+ * access, but no specific access order.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class SimpleDigitalIn implements DigitalIn {
+
+ /**
+ * Field for the digital in state.
+ */
+ protected boolean set = false;
+
+ /**
+ * Constructs a new SimpleDigitalIn instance.
+ */
+ public SimpleDigitalIn() {
+ }
+
+ /**
+ * Constructs a new SimpleDigitalIn instance
+ * with a given valid state.
+ *
+ * @param b true if to be set, false otherwise.
+ */
+ public SimpleDigitalIn(boolean b) {
+ set(b);
+ }
+
+ public boolean isSet() {
+ return set;
+ }
+
+ /**
+ * Sets the state of this SimpleDigitalIn.
+ * This method should only be used from master/device
+ * side.
+ *
+ * @param b true if to be set, false otherwise.
+ */
+ public synchronized void set(boolean b) {
+ set = b;
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalOut.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalOut.java
new file mode 100644
index 0000000..b88e690
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleDigitalOut.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Class implementing a simple DigitalOut.
+ *
+ * The set method is synchronized, which ensures atomic
+ * access, but no specific access order.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class SimpleDigitalOut implements DigitalOut {
+
+ /**
+ * Field for the digital out state.
+ */
+ protected boolean set;
+
+ /**
+ * Constructs a new SimpleDigitalOut instance.
+ * It's state will be invalid.
+ */
+ public SimpleDigitalOut() {
+ }
+
+ /**
+ * Constructs a new SimpleDigitalOut instance
+ * with the given state.
+ *
+ * @param b true if set, false otherwise.
+ */
+ public SimpleDigitalOut(boolean b) {
+ set(b);
+ }
+
+ public boolean isSet() {
+ return set;
+ }
+
+ public synchronized void set(boolean b) {
+ set = b;
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleInputRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleInputRegister.java
new file mode 100644
index 0000000..96cce0c
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleInputRegister.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Class implementing a simple InputRegister.
+ *
+ * The setValue() method is synchronized, which ensures atomic access, * but no specific access order.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class SimpleInputRegister extends SynchronizedAbstractRegister implements InputRegister {
+
+ /**
+ * Constructs a new SimpleInputRegister instance. It's state will
+ * be invalid.
+ */
+ public SimpleInputRegister() {
+ }
+
+ /**
+ * Constructs a new SimpleInputRegister instance.
+ *
+ * @param b1 the first (hi) byte of the word.
+ * @param b2 the second (low) byte of the word.
+ */
+ public SimpleInputRegister(byte b1, byte b2) {
+ register[0] = b1;
+ register[1] = b2;
+ }
+
+ /**
+ * Constructs a new SimpleInputRegister instance with the given
+ * value.
+ *
+ * @param value the value of this SimpleInputRegister as int
+ * .
+ */
+ public SimpleInputRegister(int value) {
+ setValue(value);
+ }// constructor(int)
+
+ public String toString() {
+ if (register == null) {
+ return "invalid";
+ }
+
+ return getValue() + "";
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleProcessImage.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleProcessImage.java
new file mode 100644
index 0000000..69ef723
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleProcessImage.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+import java.util.Vector;
+
+/**
+ * Class implementing a simple process image to be able to run unit tests or
+ * handle simple cases.
+ *
+ *
+ * The image has a simple linear address space for, analog, digital and file
+ * objects. Holes may be created by adding a object with a reference after the
+ * last object reference of that type.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Added support for files of records.
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class SimpleProcessImage implements ProcessImageImplementation {
+
+ // instance attributes
+ protected final Vector
+ * Compatability Note: jamod did not enforce this restriction, so it is
+ * being handled in a way which is backwards compatible. If you wish to
+ * determine if you acquired the lock, check the return value. If your code
+ * is still based on the jamod paradigm, you will ignore the return value
+ * and your code will function as before.
+ *
+ * The setValue() method is synchronized, which ensures atomic access, * but no specific access order.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class SimpleRegister extends SynchronizedAbstractRegister implements
+ Register {
+
+ /**
+ * Constructs a new SimpleRegister instance.
+ *
+ * @param b1 the first (hi) byte of the word.
+ * @param b2 the second (low) byte of the word.
+ */
+ public SimpleRegister(byte b1, byte b2) {
+ register[0] = b1;
+ register[1] = b2;
+ }
+
+ /**
+ * Constructs a new SimpleRegister instance with the given value.
+ *
+ * @param value the value of this SimpleRegister as int.
+ */
+ public SimpleRegister(int value) {
+ setValue(value);
+ }
+
+ /**
+ * Constructs a new SimpleRegister instance. It's state will be
+ * invalid.
+ *
+ * Attempting to access this register will result in an
+ * IllegalAddressException(). It may be used to create "holes" in a Modbus
+ * register map.
+ */
+ public SimpleRegister() {
+ register = null;
+ }
+
+ public String toString() {
+ if (register == null) {
+ return "invalid";
+ }
+
+ return getValue() + "";
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SynchronizedAbstractRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SynchronizedAbstractRegister.java
new file mode 100644
index 0000000..ab6f5ae
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SynchronizedAbstractRegister.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.procimg;
+
+/**
+ * Abstract class with synchronized register operations.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class SynchronizedAbstractRegister implements Register {
+
+ /**
+ * The word (byte[2]) holding the state of this register.
+ *
+ * Note that a superclass may set register to null to create a
+ * gap in a Modbus map.
+ */
+ protected byte[] register = new byte[2];
+
+ synchronized public int getValue() {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ return ((register[0] & 0xff) << 8 | (register[1] & 0xff));
+ }
+
+ public final int toUnsignedShort() {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ return ((register[0] & 0xff) << 8 | (register[1] & 0xff));
+ }
+
+ public final short toShort() {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ return (short)((register[0] << 8) | (register[1] & 0xff));
+ }
+
+ public synchronized byte[] toBytes() {
+ byte[] dest = new byte[register.length];
+ System.arraycopy(register, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ public final synchronized void setValue(short s) {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ register[0] = (byte)(0xff & (s >> 8));
+ register[1] = (byte)(0xff & s);
+ }
+
+ public final synchronized void setValue(byte[] bytes) {
+ if (bytes.length < 2) {
+ throw new IllegalArgumentException();
+ }
+ else {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ register[0] = bytes[0];
+ register[1] = bytes[1];
+ }
+ }
+
+ public final synchronized void setValue(int v) {
+ if (register == null) {
+ throw new IllegalAddressException();
+ }
+
+ register[0] = (byte)(0xff & (v >> 8));
+ register[1] = (byte)(0xff & v);
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java
new file mode 100644
index 0000000..55e94ce
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlave.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.slave;
+
+import com.ghgande.j2mod.modbus.ModbusException;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.net.ModbusTCPListener;
+import com.ghgande.j2mod.modbus.net.ModbusUDPListener;
+import com.ghgande.j2mod.modbus.procimg.ProcessImage;
+import com.ghgande.j2mod.modbus.util.ModbusUtil;
+import com.ghgande.j2mod.modbus.util.SerialParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class that implements a wrapper around a Slave Listener
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusSlave {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusSlave.class);
+
+ private ModbusSlaveType type;
+ private int port;
+ private SerialParameters serialParams;
+ private AbstractModbusListener listener;
+ private boolean isRunning;
+ private Thread listenerThread;
+
+ private Map
+ *
+ * @param size the number of bits the BitVector
+ * should be able to hold.
+ */
+ public BitVector(int size) {
+ //store bits
+ this.size = size;
+
+ //calculate size in bytes
+ if ((size % 8) > 0) {
+ size = (size / 8) + 1;
+ }
+ else {
+ size = (size / 8);
+ }
+ data = new byte[size];
+ }
+
+ /**
+ * Factory method for creating a BitVector instance
+ * wrapping the given byte data.
+ *
+ * @param data a byte[] containing packed bits.
+ * @param size Size to set the bit vector to
+ *
+ * @return the newly created BitVector instance.
+ */
+ public static BitVector createBitVector(byte[] data, int size) {
+ BitVector bv = new BitVector(data.length * 8);
+ bv.setBytes(data);
+ bv.size = size;
+ return bv;
+ }
+
+ /**
+ * Factory method for creating a BitVector instance
+ * wrapping the given byte data.
+ *
+ * @param data a byte[] containing packed bits.
+ *
+ * @return the newly created BitVector instance.
+ */
+ public static BitVector createBitVector(byte[] data) {
+ BitVector bv = new BitVector(data.length * 8);
+ bv.setBytes(data);
+ return bv;
+ }
+
+ public static void main(String[] args) {
+ BitVector test = new BitVector(24);
+ logger.debug(test.isLSBAccess() + "");
+ test.setBit(7, true);
+ logger.debug(test.getBit(7) + "");
+ test.toggleAccess(true);
+ logger.debug(test.getBit(7) + "");
+
+ test.toggleAccess(true);
+ test.setBit(6, true);
+ test.setBit(3, true);
+ test.setBit(2, true);
+
+ test.setBit(0, true);
+ test.setBit(8, true);
+ test.setBit(10, true);
+
+ logger.debug(test.toString());
+ test.toggleAccess(true);
+ logger.debug(test.toString());
+ test.toggleAccess(true);
+ logger.debug(test.toString());
+
+ logger.debug(ModbusUtil.toHex(test.getBytes()));
+ }
+
+ /**
+ * Toggles the flag deciding whether the LSB
+ * or the MSB of the byte corresponds to the
+ * first bit (index=0).
+ *
+ * @param b true if LSB=0 up to MSB=7, false otherwise.
+ */
+ public void toggleAccess(boolean b) {
+ MSBAccess = !MSBAccess;
+ }
+
+ /**
+ * Tests if this BitVector has
+ * the LSB (rightmost) as the first bit
+ * (i.e. at index 0).
+ *
+ * @return true if LSB=0 up to MSB=7, false otherwise.
+ */
+ public boolean isLSBAccess() {
+ return !MSBAccess;
+ }
+
+ /**
+ * Tests if this BitVector has
+ * the MSB (leftmost) as the first bit
+ * (i.e. at index 0).
+ *
+ * @return true if LSB=0 up to MSB=7, false otherwise.
+ */
+ public boolean isMSBAccess() {
+ return MSBAccess;
+ }
+
+ /**
+ * Returns the byte[] which is used to store
+ * the bits of this BitVector.
+ *
+ *
+ * @return the byte[] used to store the bits.
+ */
+ public synchronized final byte[] getBytes() {
+ byte[] dest = new byte[data.length];
+ System.arraycopy(data, 0, dest, 0, dest.length);
+ return dest;
+ }
+
+ /**
+ * Sets the byte[] which stores
+ * the bits of this BitVector.
+ *
+ *
+ * @param data a byte[].
+ */
+ public final void setBytes(byte[] data) {
+ System.arraycopy(data, 0, this.data, 0, data.length);
+ }
+
+ /**
+ * Sets the byte[] which stores
+ * the bits of this BitVector.
+ *
+ *
+ * @param data a byte[].
+ * @param size Size to set the bit vector to
+ */
+ public final void setBytes(byte[] data, int size) {
+ System.arraycopy(data, 0, this.data, 0, data.length);
+ this.size = size;
+ }
+
+ /**
+ * Returns the state of the bit at the given index of this
+ * BitVector.
+ *
+ *
+ * @param index the index of the bit to be returned.
+ *
+ * @return true if the bit at the specified index is set,
+ * false otherwise.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public final boolean getBit(int index) throws IndexOutOfBoundsException {
+ index = translateIndex(index);
+ logger.debug("Get bit #{}", index);
+ return ((data[byteIndex(index)]
+ & (0x01 << bitIndex(index))) != 0
+ );
+ }
+
+ /**
+ * Sets the state of the bit at the given index of
+ * this BitVector.
+ *
+ *
+ * @param index the index of the bit to be set.
+ * @param b true if the bit should be set, false if it should be reset.
+ *
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
+ */
+ public final void setBit(int index, boolean b) throws IndexOutOfBoundsException {
+ index = translateIndex(index);
+ logger.debug("Set bit #{}", index);
+ int value = ((b) ? 1 : 0);
+ int byteNum = byteIndex(index);
+ int bitNum = bitIndex(index);
+ data[byteNum] = (byte)((data[byteNum] & ~(0x01 << bitNum))
+ | ((value & 0x01) << bitNum)
+ );
+ }
+
+ /**
+ * Returns the number of bits in this BitVector
+ * as int.
+ *
+ *
+ * @return the number of bits in this BitVector.
+ */
+ public final int size() {
+ return size;
+ }
+
+ /**
+ * Forces the number of bits in this BitVector.
+ *
+ * @param size Size to set the bit vector to
+ *
+ * @throws IllegalArgumentException if the size exceeds
+ * the byte[] store size multiplied by 8.
+ */
+ public final void forceSize(int size) {
+ if (size > data.length * 8) {
+ throw new IllegalArgumentException("Size exceeds byte[] store");
+ }
+ else {
+ this.size = size;
+ }
+ }
+
+ /**
+ * Returns the number of bytes used to store the
+ * collection of bits as int.
+ *
+ *
+ * @return the number of bits in this BitVector.
+ */
+ public final int byteSize() {
+ return data.length;
+ }
+
+ /**
+ * Returns a String representing the
+ * contents of the bit collection in a way that
+ * can be printed to a screen or log.
+ *
+ * Note that this representation will ALLWAYS
+ * show the MSB to the left and the LSB to the right
+ * in each byte.
+ *
+ * @return a String representing this BitVector.
+ */
+ public String toString() {
+ StringBuilder sbuf = new StringBuilder();
+ for (int i = 0; i < data.length; i++) {
+
+ int numberOfBitsToPrint = Byte.SIZE;
+ int remainingBits = size - (i * Byte.SIZE);
+ if (remainingBits < Byte.SIZE) {
+ numberOfBitsToPrint = remainingBits;
+ }
+
+ sbuf.append(String.format("%" + numberOfBitsToPrint + "s", Integer.toBinaryString(data[i] & 0xFF)).replace(' ', '0'));
+ sbuf.append(" ");
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Returns the index of the byte in the the byte array
+ * that contains the given bit.
+ *
+ *
+ * @param index the index of the bit.
+ *
+ * @return the index of the byte where the given bit is stored.
+ *
+ * @throws IndexOutOfBoundsException if index is
+ * out of bounds.
+ */
+ private int byteIndex(int index) throws IndexOutOfBoundsException {
+
+ if (index < 0 || index >= data.length * 8) {
+ throw new IndexOutOfBoundsException();
+ }
+ else {
+ return index / 8;
+ }
+ }
+
+ /**
+ * Returns the index of the given bit in the byte
+ * where it it stored.
+ *
+ *
+ * @param index the index of the bit.
+ *
+ * @return the bit index relative to the position in the byte
+ * that stores the specified bit.
+ *
+ * @throws IndexOutOfBoundsException if index is
+ * out of bounds.
+ */
+ private int bitIndex(int index) throws IndexOutOfBoundsException {
+
+ if (index < 0 || index >= data.length * 8) {
+ throw new IndexOutOfBoundsException();
+ }
+ else {
+ return index % 8;
+ }
+ }
+
+ private int translateIndex(int idx) {
+ if (MSBAccess) {
+ int mod4 = idx % 4;
+ int div4 = idx / 4;
+
+ if ((div4 % 2) != 0) {
+ //odd
+ return (idx + ODD_OFFSETS[mod4]);
+ }
+ else {
+ //straight
+ return (idx + STRAIGHT_OFFSETS[mod4]);
+ }
+ }
+ else {
+ return idx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/ModbusUtil.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/ModbusUtil.java
new file mode 100644
index 0000000..66f5466
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/ModbusUtil.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.util;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.io.BytesOutputStream;
+import com.ghgande.j2mod.modbus.msg.ModbusMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Helper class that provides utility methods.
+ *
+ * @author Dieter Wimberger
+ * @author John Charlton
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public final class ModbusUtil {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusUtil.class);
+
+ /* Table of CRC values for high-order byte */
+ private final static short[] auchCRCHi = {
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
+ 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
+ 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
+ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
+ 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
+ };
+ /* Table of CRC values for low-order byte */
+ private final static short[] auchCRCLo = {
+ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
+ 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
+ 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
+ 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
+ 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
+ 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
+ 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
+ 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
+ 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
+ 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
+ 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
+ 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
+ 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
+ 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
+ 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
+ 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
+ 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
+ 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
+ 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
+ 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
+ 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
+ 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
+ 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
+ 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
+ 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
+ 0x43, 0x83, 0x41, 0x81, 0x80, 0x40
+ };
+
+ /**
+ * Converts a ModbusMessage instance into
+ * a hex encoded string representation.
+ *
+ * @param msg the message to be converted.
+ *
+ * @return the converted hex encoded string representation of the message.
+ */
+ public static String toHex(ModbusMessage msg) {
+ BytesOutputStream byteOutputStream = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH);
+ String ret = "-1";
+ try {
+ msg.writeTo(byteOutputStream);
+ ret = toHex(byteOutputStream.getBuffer(), 0, byteOutputStream.size());
+ }
+ catch (IOException ex) {
+ logger.debug("Hex conversion error {}", ex);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the given byte[] as hex encoded string.
+ *
+ * @param data a byte[] array.
+ *
+ * @return a hex encoded String.
+ */
+ public static String toHex(byte[] data) {
+ return toHex(data, 0, data.length);
+ }
+
+ /**
+ * Returns a String containing unsigned hexadecimal
+ * numbers as digits.
+ * The String will coontain two hex digit characters
+ * for each byte from the passed in byte[].
+ *
+ */
+ int ILLEGAL_FUNCTION_EXCEPTION = 1;
+
+ /**
+ * Defines the Modbus slave exception type illegal data address.
+ * This exception code is returned if the reference:
+ *
+ *
+ */
+ int ILLEGAL_ADDRESS_EXCEPTION = 2;
+
+ /**
+ * Defines the Modbus slave exception type illegal data value.
+ * This exception code indicates a fault in the structure of the data values
+ * of a complex request, such as an incorrect implied length.
+ * This code does not indicate a problem with application specific validity
+ * of the value.
+ */
+ int ILLEGAL_VALUE_EXCEPTION = 3;
+
+ /**
+ * Defines the Modbus slave exception type slave device failure.
+ * This exception code indicates a fault in the slave device itself.
+ */
+ int SLAVE_DEVICE_FAILURE = 4;
+
+ /**
+ * Defines the Modbus slave exception type slave busy. This
+ * exception indicates the the slave is unable to perform the operation
+ * because it is performing an operation which cannot be interrupted.
+ */
+ int SLAVE_BUSY_EXCEPTION = 6;
+
+ /**
+ * Defines the Modbus slave exception type negative acknowledgment.
+ * This exception code indicates the slave cannot perform the requested
+ * action.
+ */
+ int NEGATIVE_ACKNOWLEDGEMENT = 7;
+
+ /**
+ * Defines the Modbus slave exception type Gateway target failed to
+ * respond. This exception code indicates that a Modbus gateway
+ * failed to receive a response from the specified target.
+ */
+ int GATEWAY_TARGET_NO_RESPONSE = 11;
+
+ /**
+ * Defines the default port number of Modbus
+ * (=502).
+ */
+ int DEFAULT_PORT = 502;
+
+ /**
+ * Defines the maximum message length in bytes
+ * (=256).
+ */
+ int MAX_MESSAGE_LENGTH = 256;
+
+ /**
+ * Defines the default transaction identifier (=0).
+ */
+ int DEFAULT_TRANSACTION_ID = 0;
+
+ /**
+ * Defines the default protocol identifier (=0).
+ */
+ int DEFAULT_PROTOCOL_ID = 0;
+
+ /**
+ * Defines the default unit identifier (=0).
+ */
+ int DEFAULT_UNIT_ID = 0;
+
+ /**
+ * Defines the default setting for validity checking
+ * in transactions (=true).
+ */
+ boolean DEFAULT_VALIDITYCHECK = true;
+
+ /**
+ * Defines the default setting for I/O operation timeouts
+ * in milliseconds (=3000).
+ */
+ int DEFAULT_TIMEOUT = 3000;
+
+ /**
+ * Defines the sleep period between transaction retries
+ * in milliseconds (=200).
+ */
+ int RETRY_SLEEP_TIME = 500;
+
+ /**
+ * Defines the default reconnecting setting for
+ * transactions (=false).
+ */
+ boolean DEFAULT_RECONNECTING = false;
+
+ /**
+ * Defines the default amount of retires for opening
+ * a connection (=3).
+ */
+ int DEFAULT_RETRIES = 5;
+
+ /**
+ * Defines the default number of msec to delay before transmission
+ * Inter-message delays are managed by the SerialTransaction object automatically based on the
+ * baud rate. Setting this value to anything other than zero will bypass that process and force
+ * a specific inter-message delay
+ * (=0).
+ */
+ int DEFAULT_TRANSMIT_DELAY = 0;
+
+ /**
+ * Defines the default number of msec to delay before transmission if not overridden by DEFAULT_TRANSMIT_DELAY
+ * (=2).
+ */
+ int MINIMUM_TRANSMIT_DELAY = 2;
+
+ /**
+ * The number of characters delay that must be maintained between adjacent requests on
+ * the same serial port (within the same transaction)
+ */
+ double INTER_MESSAGE_GAP = 4;
+
+ /**
+ * Defines the maximum value of the transaction identifier.
+ *
+ *
+ * Types are defined according to the protocol specification in
+ * net.wimpi.modbus.Modbus.
+ *
+ * @return the type of this ModbusSlaveException.
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ *
+ *
+ *
+ * When the maximum value of 65535 has been reached,
+ * the identifiers will start from zero again.
+ */
+ private void incrementTransactionID() {
+ if (isCheckingValidity()) {
+ if (transactionID >= Modbus.MAX_TRANSACTION_ID) {
+ transactionID = Modbus.DEFAULT_TRANSACTION_ID;
+ }
+ else {
+ transactionID++;
+ }
+ }
+ request.setTransactionID(getTransactionID());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransport.java b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransport.java
new file mode 100644
index 0000000..501343c
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/io/ModbusUDPTransport.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.io;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.msg.ModbusMessage;
+import com.ghgande.j2mod.modbus.msg.ModbusRequest;
+import com.ghgande.j2mod.modbus.msg.ModbusResponse;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+import com.ghgande.j2mod.modbus.net.AbstractUDPTerminal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.Arrays;
+
+/**
+ * Class that implements the Modbus UDP transport flavor.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusUDPTransport extends AbstractModbusTransport {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusUDPTransport.class);
+
+ //instance attributes
+ private AbstractUDPTerminal terminal;
+ private final BytesOutputStream byteOutputStream = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH);
+ private final BytesInputStream byteInputStream = new BytesInputStream(Modbus.MAX_MESSAGE_LENGTH);
+
+ /**
+ * Constructs a new ModbusTransport instance,
+ * for a given UDPTerminal.
+ *
+ * The function code is a 1-byte non negative integer value valid in the
+ * range of 0-127.
+ *
+ *
+ *
+ * @param tid the transaction identifier as int.
+ */
+ public void setTransactionID(int tid) {
+ transactionID = tid & 0x0000FFFF;
+ }
+
+ @Override
+ public int getProtocolID() {
+ return protocolID;
+ }
+
+ /**
+ * Sets the protocol identifier of this ModbusMessage.
+ *
+ *
+ * The identifier should be a 1-byte non negative integer value valid in the
+ * range of 0-255.
+ *
+ * @param num the unit identifier number to be set.
+ */
+ public void setUnitID(int num) {
+ unitID = num;
+ }
+
+ @Override
+ public int getFunctionCode() {
+ return functionCode;
+ }
+
+ /**
+ * Sets the function code of this ModbusMessage.
+ * The function code should be a 1-byte non negative integer value valid in
+ * the range of 0-127.
+ * Function codes are ordered in conformance classes their values are
+ * specified in com.ghgande.j2mod.modbus.Modbus.
+ *
+ * @param code the code of the function to be set.
+ *
+ * @see com.ghgande.j2mod.modbus.Modbus
+ */
+ protected void setFunctionCode(int code) {
+ functionCode = code;
+ }
+
+ @Override
+ public String getHexMessage() {
+ return ModbusUtil.toHex(this);
+ }
+
+ /**
+ * Sets the headless flag of this message.
+ *
+ * @param b true if headless, false otherwise.
+ */
+ public void setHeadless(boolean b) {
+ headless = b;
+ }
+
+ @Override
+ public int getOutputLength() {
+ int l = 2 + getDataLength();
+ if (!isHeadless()) {
+ l = l + 4;
+ }
+ return l;
+ }
+
+ @Override
+ public void writeTo(DataOutput dout) throws IOException {
+
+ if (!isHeadless()) {
+ dout.writeShort(getTransactionID());
+ dout.writeShort(getProtocolID());
+ dout.writeShort(getDataLength());
+ }
+ dout.writeByte(getUnitID());
+ dout.writeByte(getFunctionCode());
+
+ writeData(dout);
+ }
+
+ @Override
+ public void readFrom(DataInput din) throws IOException {
+ if (!isHeadless()) {
+ setTransactionID(din.readUnsignedShort());
+ setProtocolID(din.readUnsignedShort());
+ dataLength = din.readUnsignedShort();
+ }
+ setUnitID(din.readUnsignedByte());
+ setFunctionCode(din.readUnsignedByte());
+ readData(din);
+ }
+
+ /**
+ * Writes the subclass specific data to the given DataOutput.
+ *
+ * @param dout the DataOutput to be written to.
+ *
+ * @throws IOException if an I/O related error occurs.
+ */
+ public abstract void writeData(DataOutput dout) throws IOException;
+
+ /**
+ * Reads the subclass specific data from the given DataInput instance.
+ *
+ * @param din the DataInput to read from.
+ *
+ * @throws IOException if an I/O related error occurs.
+ */
+ public abstract void readData(DataInput din) throws IOException;
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusRequest.java b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusRequest.java
new file mode 100644
index 0000000..286c590
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/msg/ModbusRequest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.msg;
+
+import com.ghgande.j2mod.modbus.Modbus;
+import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
+
+/**
+ * Abstract class implementing a ModbusRequest. This class provides
+ * specialised implementations with the functionality they have in common.
+ *
+ * @author Dieter Wimberger
+ * @author jfhaugh (jfh@ghgande.com)
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public abstract class ModbusRequest extends ModbusMessageImpl {
+
+ /**
+ * Factory method creating the required specialized ModbusRequest
+ * instance.
+ *
+ * @param functionCode the function code of the request as int.
+ *
+ * @return a ModbusRequest instance specific for the given function type.
+ */
+ public static ModbusRequest createModbusRequest(int functionCode) {
+ ModbusRequest request;
+
+ switch (functionCode) {
+ case Modbus.READ_COILS:
+ request = new ReadCoilsRequest();
+ break;
+ case Modbus.READ_INPUT_DISCRETES:
+ request = new ReadInputDiscretesRequest();
+ break;
+ case Modbus.READ_MULTIPLE_REGISTERS:
+ request = new ReadMultipleRegistersRequest();
+ break;
+ case Modbus.READ_INPUT_REGISTERS:
+ request = new ReadInputRegistersRequest();
+ break;
+ case Modbus.WRITE_COIL:
+ request = new WriteCoilRequest();
+ break;
+ case Modbus.WRITE_SINGLE_REGISTER:
+ request = new WriteSingleRegisterRequest();
+ break;
+ case Modbus.WRITE_MULTIPLE_COILS:
+ request = new WriteMultipleCoilsRequest();
+ break;
+ case Modbus.WRITE_MULTIPLE_REGISTERS:
+ request = new WriteMultipleRegistersRequest();
+ break;
+ case Modbus.READ_EXCEPTION_STATUS:
+ request = new ReadExceptionStatusRequest();
+ break;
+ case Modbus.READ_SERIAL_DIAGNOSTICS:
+ request = new ReadSerialDiagnosticsRequest();
+ break;
+ case Modbus.READ_COMM_EVENT_COUNTER:
+ request = new ReadCommEventCounterRequest();
+ break;
+ case Modbus.READ_COMM_EVENT_LOG:
+ request = new ReadCommEventLogRequest();
+ break;
+ case Modbus.REPORT_SLAVE_ID:
+ request = new ReportSlaveIDRequest();
+ break;
+ case Modbus.READ_FILE_RECORD:
+ request = new ReadFileRecordRequest();
+ break;
+ case Modbus.WRITE_FILE_RECORD:
+ request = new WriteFileRecordRequest();
+ break;
+ case Modbus.MASK_WRITE_REGISTER:
+ request = new MaskWriteRegisterRequest();
+ break;
+ case Modbus.READ_WRITE_MULTIPLE:
+ request = new ReadWriteMultipleRequest();
+ break;
+ case Modbus.READ_FIFO_QUEUE:
+ request = new ReadFIFOQueueRequest();
+ break;
+ case Modbus.READ_MEI:
+ request = new ReadMEIRequest();
+ break;
+ default:
+ request = new IllegalFunctionRequest(functionCode);
+ break;
+ }
+ return request;
+ }
+
+ /**
+ * Returns the ModbusResponse that correlates with this
+ * ModbusRequest.
+ *
+ *
+ *
+ * @param poolsize the size of the ThreadPool used to handle incoming
+ * requests.
+ * @param addr the interface to use for listening.
+ */
+ public ModbusTCPListener(int poolsize, InetAddress addr) {
+ this(poolsize, addr, false);
+ }
+
+ /**
+ * Constructs a ModbusTCPListener instance.
+ *
+ * @param poolsize the size of the ThreadPool used to handle incoming
+ * requests.
+ * @param addr the interface to use for listening.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public ModbusTCPListener(int poolsize, InetAddress addr, boolean useRtuOverTcp) {
+ threadPool = new ThreadPool(poolsize);
+ address = addr;
+ this.useRtuOverTcp = useRtuOverTcp;
+ }
+
+ /**
+ * /**
+ * Constructs a ModbusTCPListener instance. This interface is created
+ * to listen on the wildcard address (0.0.0.0), which will accept TCP packets
+ * on all available adapters/interfaces
+ *
+ * @param poolsize the size of the ThreadPool used to handle incoming
+ * requests.
+ */
+ public ModbusTCPListener(int poolsize) {
+ this(poolsize, false);
+ }
+
+ /**
+ * /**
+ * Constructs a ModbusTCPListener instance. This interface is created
+ * to listen on the wildcard address (0.0.0.0), which will accept TCP packets
+ * on all available adapters/interfaces
+ *
+ * @param poolsize the size of the ThreadPool used to handle incoming
+ * requests.
+ * @param useRtuOverTcp True if the RTU protocol should be used over TCP
+ */
+ public ModbusTCPListener(int poolsize, boolean useRtuOverTcp) {
+ threadPool = new ThreadPool(poolsize);
+ try {
+ address = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
+ }
+ catch (UnknownHostException ex) {
+ // Can't happen -- size is fixed.
+ }
+ this.useRtuOverTcp = useRtuOverTcp;
+ }
+
+ @Override
+ public void setTimeout(int timeout) {
+ super.setTimeout(timeout);
+ if (serverSocket != null && listening) {
+ try {
+ serverSocket.setSoTimeout(timeout);
+ }
+ catch (SocketException e) {
+ logger.error("Cannot set socket timeout", e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ /*
+ * A server socket is opened with a connectivity queue of a size
+ * specified in int floodProtection. Concurrent login handling under
+ * normal circumstances should be alright, denial of service
+ * attacks via massive parallel program logins can probably be
+ * prevented.
+ */
+ int floodProtection = 100;
+ serverSocket = new ServerSocket(port, floodProtection, address);
+ serverSocket.setSoTimeout(timeout);
+ logger.debug("Listening to {} (Port {})", serverSocket.toString(), port);
+ }
+
+ // Catch any fatal errors and set the listening flag to false to indicate an error
+ catch (Exception e) {
+ error = String.format("Cannot start TCP listener - %s", e.getMessage());
+ listening = false;
+ return;
+ }
+
+ listener = Thread.currentThread();
+ listening = true;
+ try {
+
+ // Infinite loop, taking care of resources in case of a lot of
+ // parallel logins
+ while (listening) {
+ Socket incoming;
+ try {
+ incoming = serverSocket.accept();
+ }
+ catch (SocketTimeoutException e) {
+ continue;
+ }
+ logger.debug("Making new connection {}", incoming.toString());
+ if (listening) {
+ TCPSlaveConnection slave = new TCPSlaveConnection(incoming, useRtuOverTcp);
+ slave.setTimeout(timeout);
+ threadPool.execute(new TCPConnectionHandler(this, slave));
+ }
+ else {
+ incoming.close();
+ }
+ }
+ }
+ catch (IOException e) {
+ error = String.format("Problem starting listener - %s", e.getMessage());
+ }
+ }
+
+ @Override
+ public void stop() {
+ listening = false;
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ if (listener != null) {
+ listener.join();
+ }
+ if (threadPool != null) {
+ threadPool.close();
+ }
+ }
+ catch (Exception ex) {
+ logger.error("Error while stopping ModbusTCPListener", ex);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusUDPListener.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusUDPListener.java
new file mode 100644
index 0000000..9dbca0a
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusUDPListener.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.io.ModbusUDPTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Class that implements a ModbusUDPListener.
+ *
+ * @author Dieter Wimberger
+ * @author Julie Haugh
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusUDPListener extends AbstractModbusListener {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusUDPListener.class);
+ private UDPSlaveTerminal terminal;
+
+ /**
+ * Create a new ModbusUDPListener instance listening to the given
+ * interface address.
+ *
+ * @param ifc an InetAddress instance.
+ */
+ public ModbusUDPListener(InetAddress ifc) {
+ address = ifc;
+ listening = true;
+ }
+
+ /**
+ * Constructs a new ModbusUDPListener instance. The address will be set to a
+ * default value of the wildcard local address and the default Modbus port.
+ */
+ public ModbusUDPListener() {
+ try {
+ address = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
+ }
+ catch (UnknownHostException e) {
+ // Can't happen -- length is fixed by code.
+ }
+ }
+
+ @Override
+ public void setTimeout(int timeout) {
+ super.setTimeout(timeout);
+ if (terminal != null && listening) {
+ terminal.setTimeout(timeout);
+ }
+ }
+
+ /**
+ * Starts this ModbusUDPListener.
+ */
+ @Override
+ public void run() {
+ ModbusUDPTransport transport;
+ try {
+ if (address == null) {
+ terminal = new UDPSlaveTerminal(InetAddress.getByAddress(new byte[]{0, 0, 0, 0}));
+ }
+ else {
+ terminal = new UDPSlaveTerminal(address);
+ }
+ terminal.setTimeout(timeout);
+ terminal.setPort(port);
+ terminal.activate();
+ transport = new ModbusUDPTransport(terminal);
+ }
+
+ // Catch any fatal errors and set the listening flag to false to indicate an error
+ catch (Exception e) {
+ error = String.format("Cannot start UDP listener - %s", e.getMessage());
+ listening = false;
+ return;
+ }
+
+ listening = true;
+ try {
+ while (listening) {
+ handleRequest(transport, this);
+ }
+ }
+ catch (ModbusIOException ex1) {
+ if (!ex1.isEOF()) {
+ logger.error("Exception occurred before EOF while handling request", ex1);
+ }
+ }
+ finally {
+ try {
+ terminal.deactivate();
+ transport.close();
+ }
+ catch (Exception ex) {
+ // ignore
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ terminal.deactivate();
+ listening = false;
+ }
+}
diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPConnectionHandler.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPConnectionHandler.java
new file mode 100644
index 0000000..805bc20
--- /dev/null
+++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/TCPConnectionHandler.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2016 jamod & j2mod development teams
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ghgande.j2mod.modbus.net;
+
+import com.ghgande.j2mod.modbus.ModbusIOException;
+import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class implementing a handler for incoming Modbus/TCP requests.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class TCPConnectionHandler implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(TCPConnectionHandler.class);
+
+ private TCPSlaveConnection connection;
+ private AbstractModbusTransport transport;
+ private AbstractModbusListener listener;
+
+ /**
+ * Constructs a new TCPConnectionHandler instance.
+ *
+ *
+ * Therefor implementations have to be carefully
+ * designed for concurrency.
+ *
+ * @author Dieter Wimberger
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public interface DigitalOut {
+
+ /**
+ * Tests if this DigitalOut is set.
+ *
+ * Each slave is uniquely identified by the port it is listening on, irrespective of if
+ * the socket type (TCP, UDP or Serial)
+ *
+ * @author Steve O'Hara (4NG)
+ * @version 2.0 (March 2016)
+ */
+public class ModbusSlaveFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(ModbusSlaveFactory.class);
+ private static Map
+ * The bytes will be separated by a space character.
+ *
+ * @param data the array of bytes to be converted into a hex-string.
+ * @param off the offset to start converting from.
+ * @param end the offset of the end of the byte array.
+ *
+ * @return the generated hexadecimal representation as String
.
+ */
+ public static String toHex(byte[] data, int off, int end) {
+ //double size, two bytes (hex range) for one byte
+ StringBuilder buf = new StringBuilder(data.length * 2);
+ if (end > data.length) {
+ end = data.length;
+ }
+ for (int i = off; i < end; i++) {
+ //don't forget the second hex digit
+ if (((int)data[i] & 0xff) < 0x10) {
+ buf.append("0");
+ }
+ buf.append(Long.toString((int)data[i] & 0xff, 16).toUpperCase());
+ if (i < end - 1) {
+ buf.append(" ");
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Returns a byte[] containing the given
+ * byte as unsigned hexadecimal number digits.
+ *
+ * @param i the int to be converted into a hex string.
+ *
+ * @return the generated hexadecimal representation as byte[]
.
+ */
+ public static byte[] toHex(int i) {
+ StringBuilder buf = new StringBuilder(2);
+ //don't forget the second hex digit
+ if ((i & 0xff) < 0x10) {
+ buf.append("0");
+ }
+ buf.append(Long.toString(i & 0xff, 16).toUpperCase());
+ try {
+ return buf.toString().getBytes("US-ASCII");
+ }
+ catch (Exception e) {
+ logger.debug("Problem converting bytes to string - {}", e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Converts the register (a 16 bit value) into an unsigned short.
+ * The value returned is:
+ *
+ *
+ *
+ * This conversion has been taken from the documentation of
+ * the DataInput interface.
+ *
+ * @param bytes a register as byte[2].
+ *
+ * @return the unsigned short value as int.
+ *
+ * @see java.io.DataInput
+ */
+ public static int registerToUnsignedShort(byte[] bytes) {
+ return ((bytes[0] & 0xff) << 8 | (bytes[1] & 0xff));
+ }
+
+ /**
+ * Converts the given unsigned short into a register
+ * (2 bytes).
+ * The byte values in the register, in the order
+ * shown, are:
+ *
+ * (((a & 0xff) << 8) | (b & 0xff))
+ *
+ * This conversion has been taken from the documentation of
+ * the DataOutput interface.
+ *
+ * @param v Value to convert
+ *
+ * @return the register as byte[2].
+ *
+ * @see java.io.DataOutput
+ */
+ public static byte[] unsignedShortToRegister(int v) {
+ byte[] register = new byte[2];
+ register[0] = (byte)(0xff & (v >> 8));
+ register[1] = (byte)(0xff & v);
+ return register;
+ }
+
+ /**
+ * Converts the given register (16-bit value) into
+ * a short.
+ * The value returned is:
+ *
+ *
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ *
+ *
+ * This conversion has been taken from the documentation of
+ * the DataInput interface.
+ *
+ * @param bytes bytes a register as byte[2].
+ *
+ * @return the signed short as short.
+ */
+ public static short registerToShort(byte[] bytes) {
+ return (short)((bytes[0] << 8) | (bytes[1] & 0xff));
+ }
+
+ /**
+ * Converts the register (16-bit value) at the given index
+ * into a short.
+ * The value returned is:
+ *
+ *
+ * (short)((a << 8) | (b & 0xff))
+ *
+ *
+ * This conversion has been taken from the documentation of
+ * the DataInput interface.
+ *
+ * @param bytes a byte[] containing a short value.
+ * @param idx an offset into the given byte[].
+ *
+ * @return the signed short as short.
+ */
+ public static short registerToShort(byte[] bytes, int idx) {
+ return (short)((bytes[idx] << 8) | (bytes[idx + 1] & 0xff));
+ }
+
+ /**
+ * Converts the given short into a register
+ * (2 bytes).
+ * The byte values in the register, in the order
+ * shown, are:
+ *
+ *
+ * (short)((a << 8) | (b & 0xff))
+ *
+ *
+ * @param s Value to convert
+ *
+ * @return a register containing the given short value.
+ */
+ public static byte[] shortToRegister(short s) {
+ byte[] register = new byte[2];
+ register[0] = (byte)(0xff & (s >> 8));
+ register[1] = (byte)(0xff & s);
+ return register;
+ }
+
+ /**
+ * Converts a byte[4] binary int value to a primitive int.
+ * (byte)(0xff & (v >> 8))
+ * (byte)(0xff & v)
+ *
+ * The value returned is:
+ *
+ *
+ *
+ * @param bytes registers as byte[4].
+ *
+ * @return the integer contained in the given register bytes.
+ */
+ public static int registersToInt(byte[] bytes) {
+ return (((bytes[0] & 0xff) << 24) |
+ ((bytes[1] & 0xff) << 16) |
+ ((bytes[2] & 0xff) << 8) |
+ (bytes[3] & 0xff)
+ );
+ }
+
+ /**
+ * Converts an int value to a byte[4] array.
+ *
+ * @param v the value to be converted.
+ *
+ * @return a byte[4] containing the value.
+ */
+ public static byte[] intToRegisters(int v) {
+ byte[] registers = new byte[4];
+ registers[0] = (byte)(0xff & (v >> 24));
+ registers[1] = (byte)(0xff & (v >> 16));
+ registers[2] = (byte)(0xff & (v >> 8));
+ registers[3] = (byte)(0xff & v);
+ return registers;
+ }
+
+ /**
+ * Converts a byte[8] binary long value into a long
+ * primitive.
+ *
+ * @param bytes a byte[8] containing a long value.
+ *
+ * @return a long value.
+ */
+ public static long registersToLong(byte[] bytes) {
+ return ((((long)(bytes[0] & 0xff) << 56) |
+ ((long)(bytes[1] & 0xff) << 48) |
+ ((long)(bytes[2] & 0xff) << 40) |
+ ((long)(bytes[3] & 0xff) << 32) |
+ ((long)(bytes[4] & 0xff) << 24) |
+ ((long)(bytes[5] & 0xff) << 16) |
+ ((long)(bytes[6] & 0xff) << 8) |
+ ((long)(bytes[7] & 0xff)))
+ );
+ }
+
+ /**
+ * Converts a long value to a byte[8].
+ *
+ * @param v the value to be converted.
+ *
+ * @return a byte[8] containing the long value.
+ */
+ public static byte[] longToRegisters(long v) {
+ byte[] registers = new byte[8];
+ registers[0] = (byte)(0xff & (v >> 56));
+ registers[1] = (byte)(0xff & (v >> 48));
+ registers[2] = (byte)(0xff & (v >> 40));
+ registers[3] = (byte)(0xff & (v >> 32));
+ registers[4] = (byte)(0xff & (v >> 24));
+ registers[5] = (byte)(0xff & (v >> 16));
+ registers[6] = (byte)(0xff & (v >> 8));
+ registers[7] = (byte)(0xff & v);
+ return registers;
+ }
+
+ /**
+ * Converts a byte[4] binary float value to a float primitive.
+ *
+ * @param bytes the byte[4] containing the float value.
+ *
+ * @return a float value.
+ */
+ public static float registersToFloat(byte[] bytes) {
+ return Float.intBitsToFloat((((bytes[0] & 0xff) << 24) |
+ ((bytes[1] & 0xff) << 16) |
+ ((bytes[2] & 0xff) << 8) |
+ (bytes[3] & 0xff)
+ ));
+ }
+
+ /**
+ * Converts a float value to a byte[4] binary float value.
+ *
+ * @param f the float to be converted.
+ *
+ * @return a byte[4] containing the float value.
+ */
+ public static byte[] floatToRegisters(float f) {
+ return intToRegisters(Float.floatToIntBits(f));
+ }
+
+ /**
+ * Converts a byte[8] binary double value into a double primitive.
+ *
+ * @param bytes a byte[8] to be converted.
+ *
+ * @return a double value.
+ */
+ public static double registersToDouble(byte[] bytes) {
+ return Double.longBitsToDouble(((((long)(bytes[0] & 0xff) << 56) |
+ ((long)(bytes[1] & 0xff) << 48) |
+ ((long)(bytes[2] & 0xff) << 40) |
+ ((long)(bytes[3] & 0xff) << 32) |
+ ((long)(bytes[4] & 0xff) << 24) |
+ ((long)(bytes[5] & 0xff) << 16) |
+ ((long)(bytes[6] & 0xff) << 8) |
+ ((long)(bytes[7] & 0xff)))
+ ));
+ }
+
+ /**
+ * Converts a double value to a byte[8].
+ *
+ * @param d the double to be converted.
+ *
+ * @return a byte[8].
+ */
+ public static byte[] doubleToRegisters(double d) {
+ return longToRegisters(Double.doubleToLongBits(d));
+ }
+
+ /**
+ * Converts an unsigned byte to an integer.
+ *
+ * @param b the byte to be converted.
+ *
+ * @return an integer containing the unsigned byte value.
+ */
+ public static int unsignedByteToInt(byte b) {
+ return (int)b & 0xFF;
+ }
+
+ /**
+ * Returns the low byte of an integer word.
+ *
+ * @param wd word to get low byte from
+ *
+ * @return low byte of word
+ */
+ public static byte lowByte(int wd) {
+ return Integer.valueOf(0xff & wd).byteValue();
+ }
+
+ /**
+ * @param wd word to get high byte from
+ *
+ * @return high byte
+ */
+ public static byte hiByte(int wd) {
+ return Integer.valueOf(0xff & (wd >> 8)).byteValue();
+ }
+
+ /**
+ * Makes a word from 2 bytes
+ *
+ * @param hibyte High byte
+ * @param lowbyte Low byte
+ *
+ * @return Word
+ */
+ public static int makeWord(int hibyte, int lowbyte) {
+ int hi = 0xFF & hibyte;
+ int low = 0xFF & lowbyte;
+ return ((hi << 8) | low);
+ }
+
+ public static int[] calculateCRC(byte[] data, int offset, int len) {
+
+ int[] crc = {0xFF, 0xFF};
+ int nextByte;
+ int uIndex; /* will index into CRC lookup*/ /* table */
+ /* pass through message buffer */
+ for (int i = offset; i < len && i < data.length; i++) {
+ nextByte = 0xFF & ((int)data[i]);
+ uIndex = crc[0] ^ nextByte; //*puchMsg++; /* calculate the CRC */
+ crc[0] = crc[1] ^ auchCRCHi[uIndex];
+ crc[1] = auchCRCLo[uIndex];
+ }
+
+ return crc;
+ }
+
+ /**
+ * Return true if the string is null or empty
+ *
+ * @param value String to check
+ * @return True if the value is blank or empty
+ */
+ public static boolean isBlank(String value) {
+ return value == null || value.isEmpty();
+ }
+
+ /**
+ * Return true if the list is null or empty
+ *
+ * @param list List to check
+ * @return True if the list is blank or empty
+ */
+ public static boolean isBlank(List
+ * (((a & 0xff) << 24) | ((b & 0xff) << 16) |
+ * ((c & 0xff) << 8) | (d & 0xff))
+ *