commit 2046758cc42168e0ed1a5ad20b87b14809d37f10 Author: misty Date: Wed Jun 13 00:30:36 2018 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5edb4ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000..060121f Binary files /dev/null and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c0f68ed --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..58f7131 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "com.example.user.myapp" + minSdkVersion 24 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'org.apache.commons:commons-lang3:3.7' + implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.7' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/user/myapp/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/user/myapp/ExampleInstrumentedTest.java new file mode 100644 index 0000000..b8d289f --- /dev/null +++ b/app/src/androidTest/java/com/example/user/myapp/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.user.myapp; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.user.myapp", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ca49188 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/user/myapp/DisplayMessageActivity.java b/app/src/main/java/com/example/user/myapp/DisplayMessageActivity.java new file mode 100644 index 0000000..99a4b53 --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/DisplayMessageActivity.java @@ -0,0 +1,114 @@ +package com.example.user.myapp; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.widget.TextView; + +import java.util.Calendar; +import java.util.Date; + +public class DisplayMessageActivity extends AppCompatActivity { + + private Handler handlerCoil; + private GlobalState state; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_display_message); + + LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter("readCoil8192")); + state = (GlobalState) getApplicationContext(); + + handlerCoil = new Handler(); + handlerCoil.post(refreshCoil); + } + + Runnable refreshCoil = new Runnable() { + @Override + public void run() { + Intent serviceIntent = new Intent(getApplicationContext(), MyIntentService.class); + serviceIntent.setAction("read.coil"); + serviceIntent.putExtra("extra.ip.address", state.getIpAddress()); + serviceIntent.putExtra("extra.ip.port", state.getPort()); + serviceIntent.putExtra("extra.ref", state.getCoilRef()); + serviceIntent.putExtra("extra.count", state.getCoilCount()); + serviceIntent.putExtra("extra.intent.name", "readCoil8192"); + getApplicationContext().startService(serviceIntent); + + handlerCoil.postDelayed(this, 2000); + } + }; + + Runnable refreshDiscretInput = new Runnable() { + @Override + public void run() { + // TODO Auto-generated method stub + TextView textView = findViewById(R.id.textView1); + Date currentTime = Calendar.getInstance().getTime(); + textView.setText(new Double(Math.random()).toString()); + handlerCoil.postDelayed(this, 1000); + } + }; + + Runnable refreshInputRegister = new Runnable() { + @Override + public void run() { + // TODO Auto-generated method stub + TextView textView = findViewById(R.id.textView1); + Date currentTime = Calendar.getInstance().getTime(); + textView.setText(new Double(Math.random()).toString()); + handlerCoil.postDelayed(this, 1000); + } + }; + + Runnable refreshHoldingRegister = new Runnable() { + @Override + public void run() { + // TODO Auto-generated method stub + TextView textView = findViewById(R.id.textView1); + Date currentTime = Calendar.getInstance().getTime(); + textView.setText(new Double(Math.random()).toString()); + handlerCoil.postDelayed(this, 1000); + } + }; + + private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Bundle bundle = intent.getExtras(); + boolean[] booleanArray = bundle.getBooleanArray("values"); + + TextView textView1 = findViewById(R.id.textView1); + textView1.setText(new Boolean(booleanArray[0]).toString()); + + TextView textView2 = findViewById(R.id.textView2); + textView2.setText(new Boolean(booleanArray[1]).toString()); + + TextView textView3 = findViewById(R.id.textView3); + textView3.setText(new Boolean(booleanArray[2]).toString()); + + TextView textView4 = findViewById(R.id.textView4); + textView4.setText(new Boolean(booleanArray[3]).toString()); + + TextView textView5 = findViewById(R.id.textView5); + textView5.setText(new Boolean(booleanArray[4]).toString()); + + TextView textView6 = findViewById(R.id.textView6); + textView6.setText(new Boolean(booleanArray[5]).toString()); + + TextView textView7 = findViewById(R.id.textView7); + textView7.setText(new Boolean(booleanArray[6]).toString()); + + TextView textView8 = findViewById(R.id.textView8); + textView8.setText(new Boolean(booleanArray[7]).toString()); + } + }; +} diff --git a/app/src/main/java/com/example/user/myapp/GlobalState.java b/app/src/main/java/com/example/user/myapp/GlobalState.java new file mode 100644 index 0000000..ad8f217 --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/GlobalState.java @@ -0,0 +1,51 @@ +package com.example.user.myapp; + +import android.app.Application; + +import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster; +import com.ghgande.j2mod.modbus.net.TCPMasterConnection; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class GlobalState extends Application { + + // coil + private String ipAddress; + private int port = 502; + private int coilRef; + private int coilCount; + + // getter/setter + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getCoilCount() { + return coilCount; + } + + public void setCoilCount(int coilCount) { + this.coilCount = coilCount; + } + + public void setCoilRef(int coilRef) { + this.coilRef = coilRef; + } + + public int getCoilRef() { + return coilRef; + } +} diff --git a/app/src/main/java/com/example/user/myapp/MainActivity.java b/app/src/main/java/com/example/user/myapp/MainActivity.java new file mode 100644 index 0000000..44f1515 --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/MainActivity.java @@ -0,0 +1,52 @@ +package com.example.user.myapp; + +import android.content.Intent; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.EditText; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + } + + /** Called when the user taps the Send button */ + public void sendMessage(View view) { + EditText editText1 = (EditText) findViewById(R.id.ipAddress); + String ipAddress = editText1.getText().toString(); + + EditText editText2 = (EditText) findViewById(R.id.port); + int port = Integer.parseInt(editText2.getText().toString()); + + EditText editText3 = (EditText) findViewById(R.id.ref); + int ref = Integer.parseInt(editText3.getText().toString()); + + EditText editText4 = (EditText) findViewById(R.id.count); + int count = Integer.parseInt(editText4.getText().toString()); + + + GlobalState state = (GlobalState) getApplicationContext(); + state.setIpAddress(ipAddress); + state.setPort(port); + state.setCoilRef(ref); + state.setCoilCount(count); + + Intent serviceIntent = new Intent(this, MyIntentService.class); + serviceIntent.setAction("read.coil"); + serviceIntent.putExtra("extra.ip.address", ipAddress); + serviceIntent.putExtra("extra.ip.port", port); + serviceIntent.putExtra("extra.ref", ref); + serviceIntent.putExtra("extra.count", count); + serviceIntent.putExtra("extra.intent.name", "readCoil8192"); + this.startService(serviceIntent); + + Intent nextIntent = new Intent(this, DisplayMessageActivity.class); + this.startActivity(nextIntent); + } +} + diff --git a/app/src/main/java/com/example/user/myapp/ManualDriveActivity.java b/app/src/main/java/com/example/user/myapp/ManualDriveActivity.java new file mode 100644 index 0000000..3e9138d --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/ManualDriveActivity.java @@ -0,0 +1,13 @@ +package com.example.user.myapp; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +public class ManualDriveActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_manual_drive); + } +} diff --git a/app/src/main/java/com/example/user/myapp/MeasurementActivity.java b/app/src/main/java/com/example/user/myapp/MeasurementActivity.java new file mode 100644 index 0000000..bf1cf54 --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/MeasurementActivity.java @@ -0,0 +1,20 @@ +package com.example.user.myapp; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +public class MeasurementActivity extends AppCompatActivity { + + Boolean myBoolean = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_measurement); + } + + + public boolean toggle () { + return myBoolean ? false: true; + } +} diff --git a/app/src/main/java/com/example/user/myapp/MyIntentService.java b/app/src/main/java/com/example/user/myapp/MyIntentService.java new file mode 100644 index 0000000..07dd479 --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/MyIntentService.java @@ -0,0 +1,115 @@ +package com.example.user.myapp; + +import android.app.IntentService; +import android.content.Intent; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; + +import com.ghgande.j2mod.modbus.ModbusException; +import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster; +import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; +import com.ghgande.j2mod.modbus.net.TCPMasterConnection; +import com.ghgande.j2mod.modbus.util.BitVector; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +/** + * An {@link IntentService} subclass for handling asynchronous task requests in + * a service on a separate handler thread. + *

+ * TODO: Customize class - update intent actions, extra parameters and static + * helper methods. + */ +public class MyIntentService extends IntentService { + // TODO: Rename actions, choose action names that describe tasks that this + // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS + // private static final String ACTION_FOO = "com.example.user.myapp.action.FOO"; + private static final String ACTION_READ_COIL = "read.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_PARAM2 = "com.example.user.myapp.extra.PARAM2"; + + public MyIntentService() { + super("MyIntentService"); + } + + private void sendBooleanListToActivity(List booleanList, String intentName) { + Intent intent = new Intent(intentName); + Bundle bundle = new Bundle(); + + bundle.putBooleanArray("values", toPrimitiveArray(booleanList)); + intent.putExtras(bundle); + sendLocationBroadcast(intent); + } + + private void sendLocationBroadcast(Intent intent) { + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } + + public void readCoilAction (Context context, String ipAddress, int port, int ref, int count, String intentReceiveName) { + try { + master = new ModbusTCPMaster(ipAddress, port); + master.connect(); + + BitVector bv = master.readCoils(ref,count); + List listBooleanBit = new ArrayList<>(); + for (int i = 0; i < count; i++) { + listBooleanBit.add(bv.getBit(i)); + } + sendBooleanListToActivity(listBooleanBit, intentReceiveName); + master.disconnect(); + } catch (Exception e) { + System.out.println("Exception in reading coil " + e); + } + } + + @Override + protected void onHandleIntent(Intent intent) { + GlobalState state = (GlobalState) getApplicationContext(); + if (intent != null) { + final String action = intent.getAction(); + final String ipAddress = intent.getStringExtra(EXTRA_IP_ADDRESS); + final int port = intent.getIntExtra(EXTRA_PORT, 502); + final int ref = intent.getIntExtra(EXTRA_REF, 8192); + final int count = intent.getIntExtra(EXTRA_COUNT, 8); + final String intentName = intent.getStringExtra(EXTRA_INTENT_NAME); + + switch(action) { + //case ACTION_FOO : + // handleActionFoo(param1, param2); + // break; + case ACTION_READ_COIL : + handleReadCoilAction(ipAddress, port, ref, count, intentName); + } + } + } + + /** + * Handle action Baz in the provided background thread with the provided + * parameters. + */ + private void handleReadCoilAction(String ipAddress, int port, int ref, int count, String intentName) { + readCoilAction(this, ipAddress, port, ref, count, intentName); + } + + private boolean[] toPrimitiveArray(final List booleanList) { + final boolean[] primitives = new boolean[booleanList.size()]; + int index = 0; + for (Boolean object : booleanList) { + primitives[index++] = object; + } + return primitives; + } +} diff --git a/app/src/main/java/com/example/user/myapp/MyTest.java b/app/src/main/java/com/example/user/myapp/MyTest.java new file mode 100644 index 0000000..7986c6c --- /dev/null +++ b/app/src/main/java/com/example/user/myapp/MyTest.java @@ -0,0 +1,89 @@ +package com.example.user.myapp; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import com.ghgande.j2mod.modbus.ModbusException; +import com.ghgande.j2mod.modbus.facade.ModbusTCPMaster; +import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; +import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; +import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest; +import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse; +import com.ghgande.j2mod.modbus.net.TCPMasterConnection; +import com.ghgande.j2mod.modbus.util.BitVector; + +import java.net.InetAddress; + +public class MyTest extends AsyncTask { + + // private ModbusTCPMaster master = null; + TCPMasterConnection connection = null; + Activity prevActivityContext = null; + + public MyTest (Activity context, TCPMasterConnection connection) { + this.connection = connection; + this.prevActivityContext = context; + } + + @Override + protected String doInBackground(String...strings) { + System.out.println("Pass !! "); + boolean open = false; + try { + // master = new ModbusTCPMaster(strings[0], 503); + + // InetAddress inetAddress = InetAddress.getByName("192.168.157.16"); + // TCPMasterConnection connection = new TCPMasterConnection(inetAddress); + // connection.setPort(503); + connection.connect(); + open = connection.isConnected(); +// + System.out.print("am i open : "+open); + + + + ReadCoilsRequest request = new ReadCoilsRequest(8192, 8); + + ModbusTransaction transaction = new ModbusTCPTransaction(connection); + transaction.setRequest(request); + transaction.execute(); + + BitVector bv = ((ReadCoilsResponse) getAndCheckResponse(transaction)).getCoils(); + // bv.forceSize(count); + System.out.println("my bit :"+bv.getBit(0)); + connection.close(); + + // this.master.connect(); + // master.connect(); + // return null; + return "i'm connected"; + } catch (Exception e) { + return e.getMessage(); + } + } + + protected void onPostExecute(String string) { + // // try { + System.out.println("finished"); + System.out.println(string); + + Intent intent = new Intent(this.prevActivityContext, DisplayMessageActivity.class); + // intent.putExtra("connection", (Object) this.master); + // System.out.println (master); + + prevActivityContext.startActivity(intent); + + } + + + private ModbusResponse getAndCheckResponse(ModbusTransaction transaction) throws ModbusException { + ModbusResponse res = transaction.getResponse(); + if (res == null) { + throw new ModbusException("No response"); + } + return res; + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/Modbus.java b/app/src/main/java/com/ghgande/j2mod/modbus/Modbus.java new file mode 100644 index 0000000..3ab0c71 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/Modbus.java @@ -0,0 +1,349 @@ +/* + * 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; + +/** + * Interface defining all constants related to the + * Modbus protocol. + * + * @author Dieter Wimberger + * @version 1.2rc1 (09/11/2004) + */ +public interface Modbus { + + /** + * Defines the class 1 function code + * for read coils. + */ + int READ_COILS = 1; + + /** + * Defines a class 1 function code + * for read input discretes. + */ + int READ_INPUT_DISCRETES = 2; + + /** + * Defines a class 1 function code + * for read holding registers + */ + int READ_HOLDING_REGISTERS = 3; + + /** + * Defines the class 0 function code + * for read multiple registers. The + * proper name is "Read Holding Registers". + */ + int READ_MULTIPLE_REGISTERS = 3; + + /** + * Defines a class 1 function code + * for read input registers. + */ + int READ_INPUT_REGISTERS = 4; + + /** + * Defines a class 1 function code + * for write coil. + */ + int WRITE_COIL = 5; + + /** + * Defines a class 1 function code + * for write single register. + */ + int WRITE_SINGLE_REGISTER = 6; + + /** + * read exception status + * + * Serial devices only. + */ + int READ_EXCEPTION_STATUS = 7; + + /** + * get serial diagnostics + * + * Serial devices only. + */ + int READ_SERIAL_DIAGNOSTICS = 8; + + /** + * get comm event counter + * + * Serial devices only. + */ + int READ_COMM_EVENT_COUNTER = 11; + + /** + * get comm event log + * + * Serial devices only. + */ + int READ_COMM_EVENT_LOG = 12; + + /** + * Defines a standard function code + * for write multiple coils. + */ + int WRITE_MULTIPLE_COILS = 15; + + /** + * Defines the class 0 function code + * for write multiple registers. + */ + int WRITE_MULTIPLE_REGISTERS = 16; + + /** + * Defines a standard function code + * for read slave ID. + */ + int REPORT_SLAVE_ID = 17; + + /** + * read file record + */ + int READ_FILE_RECORD = 20; + + /** + * write file record + */ + int WRITE_FILE_RECORD = 21; + + /** + * mask write register + * + * Update a single register using its current value and an AND + * and OR mask. + */ + int MASK_WRITE_REGISTER = 22; + + /** + * read / write multiple registers + * + * Write some number of registers, then read some number of + * potentially other registers back. + */ + int READ_WRITE_MULTIPLE = 23; + + /** + * read FIFO queue + * + * Read from a FIFO queue. + */ + int READ_FIFO_QUEUE = 24; + + /** + * Defines the function code for reading + * encapsulated data, such as vendor information. + */ + int READ_MEI = 43; + int READ_MEI_VENDOR_INFO = 14; + + /** + * Defines the byte representation of the coil state on. + */ + int COIL_ON = (byte)255; + + /** + * Defines the byte representation of the coil state pos. + */ + int COIL_OFF = 0; + + /** + * Defines the word representation of the coil state on. + */ + byte[] COIL_ON_BYTES = {(byte)COIL_ON, (byte)COIL_OFF}; + + /** + * Defines the word representation of the coil state pos. + */ + byte[] COIL_OFF_BYTES = {(byte)COIL_OFF, (byte)COIL_OFF}; + + /** + * Defines the maximum number of bits in multiple read/write + * of input discretes or coils (2000). + */ + int MAX_BITS = 2000; + + /** + * Defines the Modbus slave exception offset that is added to the + * function code, to flag an exception. + */ + int EXCEPTION_OFFSET = 128; //the last valid function code is 127 + + /** + * Defines the Modbus slave exception type illegal function. + * This exception code is returned if the slave: + *

+ */ + 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. + * + *

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.
+ * Types are defined according to the protocol specification in + * net.wimpi.modbus.Modbus. + * + * @return the type of this ModbusSlaveException. + */ + public int getType() { + return type; + } + + /** + *

+ * 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.
+ * 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. + *

+ * + * @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.
+ * The function code is 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. + * + * @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.
+ * + * @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 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 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. + * + *

+ * 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). + *

+ * + * @author Dieter Wimberger + * @author Julie Haugh + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public final class WriteMultipleCoilsRequest extends ModbusRequest { + + // instance attributes + private int reference; + private BitVector coils; + + /** + * Constructs a new WriteMultipleCoilsRequest instance with the + * given reference and coil values. + * + * @param ref the index of the first coil to be written. + * @param bv the coil values to be written. + */ + public WriteMultipleCoilsRequest(int ref, BitVector bv) { + super(); + + setFunctionCode(Modbus.WRITE_MULTIPLE_COILS); + setDataLength(bv.byteSize() + 5); + + setReference(ref); + coils = bv; + } + + /** + * Constructs a new WriteMultipleCoilsRequest instance with a given + * reference and count of coils to be written, followed by the actual byte + * count, and then count number of bytes. + * + * @param ref the index of the first coil to be written. + * @param count the number of coils to be written. + */ + public WriteMultipleCoilsRequest(int ref, int count) { + super(); + + setFunctionCode(Modbus.WRITE_MULTIPLE_COILS); + setDataLength((count + 7) / 8 + 5); + + setReference(ref); + coils = new BitVector(count); + } + + /** + * Constructs a new WriteMultipleCoilsRequest instance. + * + *

+ * 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 getCommPorts(); + +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractUDPTerminal.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractUDPTerminal.java new file mode 100644 index 0000000..106b7d3 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/AbstractUDPTerminal.java @@ -0,0 +1,123 @@ +/* + * 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.ModbusUDPTransport; + +import java.net.DatagramSocket; +import java.net.InetAddress; + +/** + * Interface defining a UDPTerminal. + * + * @author Dieter Wimberger + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public abstract class AbstractUDPTerminal { + + protected InetAddress address; + protected ModbusUDPTransport transport; + protected boolean active; + protected int port = Modbus.DEFAULT_PORT; + protected int timeout = Modbus.DEFAULT_TIMEOUT; + protected DatagramSocket socket; + + /** + * Gets the local adapter address + * + * @return Adapter address + */ + public InetAddress getAddress() { + return address; + } + + /** + * Returns the local port the terminal is listening on + * + * @return Port number + */ + public synchronized int getPort() { + return port; + } + + /** + * Sets the local port the terminal is running on + * + * @param port Local port + */ + protected synchronized void setPort(int port) { + this.port = port; + } + + /** + * Tests if this UDPSlaveTerminal is active. + * + * @return true if active, false otherwise. + */ + public boolean isActive() { + return active; + } + + /** + * Sets the timeout in milliseconds for this UDPSlaveTerminal. + * + * @param timeout the timeout as int. + */ + public synchronized void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Get the transport + * @return Transport + */ + public ModbusUDPTransport getTransport() { + return transport; + } + + /** + * Activate this UDPTerminal. + * + * @throws Exception if there is a network failure. + */ + public abstract void activate() throws Exception; + + /** + * Deactivates this UDPTerminal. + */ + public abstract void deactivate(); + + /** + * Sends the given message. + * + * @param msg the message as byte[]. + * + * @throws Exception if sending the message fails. + */ + public abstract void sendMessage(byte[] msg) throws Exception; + + /** + * Receives and returns a message. + * + * @return the message as a newly allocated byte[]. + * + * @throws Exception if receiving a message fails. + */ + public abstract byte[] receiveMessage() throws Exception; + +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusTCPListener.java b/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusTCPListener.java new file mode 100644 index 0000000..7403d5b --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/net/ModbusTCPListener.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.net; + +import com.ghgande.j2mod.modbus.util.ThreadPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.*; + +/** + * Class that implements a ModbusTCPListener. + *

+ * 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.
+ * + * @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. + * + *

+ * 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 requests = new Hashtable(342); + private LinkedBlockingQueue sendQueue = new LinkedBlockingQueue(); + private LinkedBlockingQueue receiveQueue = new LinkedBlockingQueue(); + private PacketSender packetSender; + private PacketReceiver packetReceiver; + + /** + * Creates a slave terminal on the specified adapter address + * Use 0.0.0.0 to listen on all adapters + * + * @param localaddress Local address to bind to + */ + protected UDPSlaveTerminal(InetAddress localaddress) { + address = localaddress; + } + + @Override + public synchronized void activate() throws Exception { + if (!isActive()) { + logger.debug("UDPSlaveTerminal.activate()"); + if (address != null && port != -1) { + socket = new DatagramSocket(port, address); + } + else { + socket = new DatagramSocket(); + port = socket.getLocalPort(); + address = socket.getLocalAddress(); + } + logger.debug("UDPSlaveTerminal::haveSocket():{}", socket.toString()); + logger.debug("UDPSlaveTerminal::addr=:{}:port={}", address.toString(), port); + + socket.setReceiveBufferSize(1024); + socket.setSendBufferSize(1024); + + // Never timeout the receive + socket.setSoTimeout(0); + + // Start a sender + packetSender = new PacketSender(socket); + new Thread(packetSender).start(); + logger.debug("UDPSlaveTerminal::sender started()"); + + // Start a receiver + + packetReceiver = new PacketReceiver(socket); + new Thread(packetReceiver).start(); + logger.debug("UDPSlaveTerminal::receiver started()"); + + // Create a transport to use + + transport = new ModbusUDPTransport(this); + logger.debug("UDPSlaveTerminal::transport created"); + active = true; + } + logger.debug("UDPSlaveTerminal::activated"); + } + + @Override + public synchronized void deactivate() { + try { + if (active) { + // Stop receiver - this will stop and close the socket + packetReceiver.stop(); + + // Stop sender gracefully + packetSender.stop(); + + transport = null; + active = false; + } + } + catch (Exception ex) { + logger.error("Error deactivating UDPSlaveTerminal", ex); + } + } + + @Override + public void sendMessage(byte[] msg) throws Exception { + sendQueue.add(msg); + } + + @Override + public byte[] receiveMessage() throws Exception { + return receiveQueue.take(); + } + + /** + * The background thread that is responsible for sending messages in response to requests + */ + class PacketSender implements Runnable { + + private boolean running; + private boolean closed; + private Thread thread; + private DatagramSocket socket; + + /** + * Constructs a sender with th socket + * + * @param socket Socket to use + */ + public PacketSender(DatagramSocket socket) { + running = true; + this.socket = socket; + } + + /** + * Stops the sender + */ + public void stop() { + running = false; + thread.interrupt(); + while (!closed) { + ModbusUtil.sleep(100); + } + } + + /** + * Thread loop that sends messages + */ + public void run() { + closed = false; + thread = Thread.currentThread(); + do { + try { + // Pickup the message and corresponding request + byte[] message = sendQueue.take(); + DatagramPacket req = requests.remove(ModbusUtil.registersToInt(message)); + + // Create new Package with corresponding address and port + if (req != null) { + DatagramPacket res = new DatagramPacket(message, message.length, req.getAddress(), req.getPort()); + socket.send(res); + logger.debug("Sent package from queue"); + } + } + catch (Exception ex) { + // Ignore the error if we are no longer listening + + if (running) { + logger.error("Problem reading UDP socket", ex); + } + } + } while (running); + closed = true; + } + + } + + /** + * The background thread that receives messages and adds them to the process list + * for further analysis + */ + class PacketReceiver implements Runnable { + + private boolean running; + private boolean closed; + private DatagramSocket socket; + + /** + * A receiver thread for reception of UDP messages + * + * @param socket Socket to use + */ + public PacketReceiver(DatagramSocket socket) { + running = true; + this.socket = socket; + } + + /** + * Stops the thread + */ + public void stop() { + running = false; + socket.close(); + while (!closed) { + ModbusUtil.sleep(100); + } + } + + /** + * Background thread for reading UDP messages + */ + public void run() { + closed = false; + do { + try { + // 1. Prepare buffer and receive package + byte[] buffer = new byte[256];// max size + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + socket.receive(packet); + + // 2. Extract TID and remember request + Integer tid = ModbusUtil.registersToInt(buffer); + requests.put(tid, packet); + + // 3. place the data buffer in the queue + receiveQueue.put(buffer); + logger.debug("Received package to queue"); + } + catch (Exception ex) { + // Ignore the error if we are no longer listening + + if (running) { + logger.error("Problem reading UDP socket", ex); + } + } + } while (running); + closed = true; + } + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/AbstractRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/AbstractRegister.java new file mode 100644 index 0000000..8eda8c9 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/AbstractRegister.java @@ -0,0 +1,70 @@ +/* + * 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 for a register. + * + * @author Dieter Wimberger + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public abstract class AbstractRegister implements Register { + + /** + * The word (byte[2]) holding the register content. + */ + protected byte[] register = new byte[2]; + + public int getValue() { + return ((register[0] & 0xff) << 8 | (register[1] & 0xff)); + } + + public final int toUnsignedShort() { + return ((register[0] & 0xff) << 8 | (register[1] & 0xff)); + } + + public final short toShort() { + 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 void setValue(short s) { + register[0] = (byte)(0xff & (s >> 8)); + register[1] = (byte)(0xff & s); + } + + public final void setValue(byte[] bytes) { + if (bytes.length < 2) { + throw new IllegalArgumentException(); + } + else { + register[0] = bytes[0]; + register[1] = bytes[1]; + } + } + + public final void setValue(int v) { + register[0] = (byte)(0xff & (v >> 8)); + register[1] = (byte)(0xff & v); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DefaultProcessImageFactory.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DefaultProcessImageFactory.java new file mode 100644 index 0000000..aef428c --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DefaultProcessImageFactory.java @@ -0,0 +1,140 @@ +/* + * 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; + +/** + * The default ProcessImageFactory. It creates a new SimpleProcessImage + * each time createProcessImageImplementation() is invoked. + * + * @author Dieter Wimberger + * @author jfhaugh + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public class DefaultProcessImageFactory implements ProcessImageFactory { + + /** + * Returns a new SimpleProcessImage instance. + * + * @return a SimpleProcessImage instance. + */ + public ProcessImageImplementation createProcessImageImplementation() { + return new SimpleProcessImage(); + } + + /** + * Returns a new SimpleDigitalIn instance. + * + * @return a SimpleDigitalIn instance. + */ + public DigitalIn createDigitalIn() { + return new SimpleDigitalIn(); + } + + /** + * Returns a new DigitalIn instance with the given state. + * + * @param state true if set, false otherwise. + * + * @return a SimpleDigitalIn instance. + */ + public DigitalIn createDigitalIn(boolean state) { + return new SimpleDigitalIn(state); + } + + /** + * Returns a new SimpleDigitalOut instance. + * + * @return a SimpleDigitalOut instance. + */ + public DigitalOut createDigitalOut() { + return new SimpleDigitalOut(); + } + + /** + * Returns a new DigitalOut instance with the given state. + * + * @param b true if set, false otherwise. + * + * @return a SimpleDigitalOut instance. + */ + public DigitalOut createDigitalOut(boolean b) { + return new SimpleDigitalOut(b); + } + + /** + * Returns a new SimpleInputRegister instance. + * + * @return a SimpleInputRegister instance. + */ + public InputRegister createInputRegister() { + return new SimpleInputRegister(); + } + + /** + * Returns a new InputRegister instance with a given value. + * + * @param b1 the first byte. + * @param b2 the second byte. + * + * @return an InputRegister instance. + */ + public InputRegister createInputRegister(byte b1, byte b2) { + return new SimpleInputRegister(b1, b2); + } + + /** + * Creates a new SimpleRegister instance. + * + * @return a SimpleRegister instance. + */ + public Register createRegister() { + return new SimpleRegister(); + } + + /** + * Returns a new Register instance with a given value. + * + * @param b1 the first byte. + * @param b2 the second byte. + * + * @return a Register instance. + */ + public Register createRegister(byte b1, byte b2) { + return new SimpleRegister(b1, b2); + } + + /** + * Returns a new InputRegister instance with a given value. + * + * @param value the value of the register as an int + * + * @return an InputRegister instance. + */ + public InputRegister createInputRegister(int value) { + return new SimpleInputRegister(value); + } + + /** + * Creates a new SimpleRegister instance. + * + * @param value initial value of the register + * @return a SimpleRegister instance. + */ + public Register createRegister(int value) { + return new SimpleRegister(value); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalIn.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalIn.java new file mode 100644 index 0000000..beacb8e --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/DigitalIn.java @@ -0,0 +1,39 @@ +/* + * 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 input. + *

+ * 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.
+ * 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. + *

+ * + * @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 registers; + + public FIFO(int address) { + this.address = address; + registerCount = 0; + registers = new Vector(); + } + + public synchronized int getRegisterCount() { + return registerCount; + } + + public synchronized Register[] getRegisters() { + Register result[] = new Register[registerCount + 1]; + + result[0] = new SimpleRegister(registerCount); + for (int i = 0; i < registerCount; i++) { + result[i + 1] = registers.get(i); + } + + return result; + } + + public synchronized void pushRegister(Register register) { + if (registerCount == 31) { + registers.remove(0); + } + else { + registerCount++; + } + + registers.add(new SimpleRegister(register.getValue())); + } + + public synchronized void resetRegisters() { + registers.removeAllElements(); + registerCount = 0; + } + + public int getAddress() { + return address; + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/File.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/File.java new file mode 100644 index 0000000..4728706 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/File.java @@ -0,0 +1,68 @@ +/* + * 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 File, as supported by the + * READ FILE RECORD and WRITE FILE RECORD commands. + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public class File { + + private static final Logger logger = LoggerFactory.getLogger(File.class); + + private int fileNumber; + private int record_Count; + private Record records[]; + + public File(int fileNumber, int records) { + this.fileNumber = fileNumber; + record_Count = records; + this.records = new Record[records]; + } + + public int getFileNumber() { + return fileNumber; + } + + public int getRecordCount() { + return record_Count; + } + + public Record getRecord(int i) { + if (i < 0 || i >= record_Count) { + throw new IllegalAddressException(); + } + + return records[i]; + } + + public File setRecord(int i, Record record) { + if (i < 0 || i >= record_Count) { + throw new IllegalAddressException(); + } + + records[i] = record; + + return this; + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/IllegalAddressException.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/IllegalAddressException.java new file mode 100644 index 0000000..79c19eb --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/IllegalAddressException.java @@ -0,0 +1,50 @@ +/* + * 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 an IllegalAddressException. This exception is + * thrown when a non-existant spot in the process image was addressed. + *

+ * 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 digitalIns = new Vector(); + protected final Vector digitalOuts = new Vector(); + protected final Vector inputRegisters = new Vector(); + protected final Vector registers = new Vector(); + protected final Vector files = new Vector(); + protected final Vector fifos = new Vector(); + protected boolean locked = false; + protected int unitID = 0; + + /** + * Constructs a new SimpleProcessImage instance. + */ + public SimpleProcessImage() { + } + + /** + * Constructs a new SimpleProcessImage instance having a + * (potentially) non-zero unit ID. + * @param unit Unit ID of this image + */ + public SimpleProcessImage(int unit) { + unitID = unit; + } + + /** + * The process image is locked to prevent changes. + * + * @return whether or not the process image is locked. + */ + public synchronized boolean isLocked() { + return locked; + } + + /** + * setLocked -- lock or unlock the process image. It is an error (false + * return value) to attempt to lock the process image when it is already + * locked. + * + *

+ * 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. + *

+ * @param locked True if the image is to be locked + * @return setting lock succeded + */ + public synchronized boolean setLocked(boolean locked) { + if (this.locked && locked) { + return false; + } + + this.locked = locked; + return true; + } + + public int getUnitID() { + return unitID; + } + + public DigitalOut[] getDigitalOutRange(int ref, int count) { + // ensure valid reference range + if (ref < 0 || ref + count > digitalOuts.size()) { + throw new IllegalAddressException(); + } + else { + DigitalOut[] douts = new DigitalOut[count]; + for (int i = 0; i < douts.length; i++) { + douts[i] = getDigitalOut(ref + i); + } + return douts; + } + } + + public DigitalOut getDigitalOut(int ref) throws IllegalAddressException { + try { + DigitalOut result = digitalOuts.elementAt(ref); + if (result == null) { + throw new IllegalAddressException(); + } + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public int getDigitalOutCount() { + return digitalOuts.size(); + } + + public DigitalIn[] getDigitalInRange(int ref, int count) { + // ensure valid reference range + if (ref < 0 || ref + count > digitalIns.size()) { + throw new IllegalAddressException(); + } + else { + DigitalIn[] dins = new DigitalIn[count]; + for (int i = 0; i < dins.length; i++) { + dins[i] = getDigitalIn(ref + i); + } + return dins; + } + } + + public DigitalIn getDigitalIn(int ref) throws IllegalAddressException { + try { + DigitalIn result = digitalIns.elementAt(ref); + if (result == null) { + throw new IllegalAddressException(); + } + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public int getDigitalInCount() { + return digitalIns.size(); + } + + public InputRegister[] getInputRegisterRange(int ref, int count) { + // ensure valid reference range + if (ref < 0 || ref + count > inputRegisters.size()) { + throw new IllegalAddressException(); + } + + InputRegister[] iregs = new InputRegister[count]; + for (int i = 0; i < iregs.length; i++) { + iregs[i] = getInputRegister(ref + i); + } + + return iregs; + } + + public InputRegister getInputRegister(int ref) throws IllegalAddressException { + try { + InputRegister result = inputRegisters.elementAt(ref); + if (result == null) { + throw new IllegalAddressException(); + } + + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public int getInputRegisterCount() { + return inputRegisters.size(); + } + + public Register[] getRegisterRange(int ref, int count) { + if (ref < 0 || ref + count > registers.size()) { + throw new IllegalAddressException(); + } + else { + Register[] iregs = new Register[count]; + for (int i = 0; i < iregs.length; i++) { + iregs[i] = getRegister(ref + i); + } + return iregs; + } + } + + public Register getRegister(int ref) throws IllegalAddressException { + try { + Register result = registers.elementAt(ref); + if (result == null) { + throw new IllegalAddressException(); + } + + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public int getRegisterCount() { + return registers.size(); + } + + public File getFile(int fileNumber) { + try { + File result = files.elementAt(fileNumber); + if (result == null) { + throw new IllegalAddressException(); + } + + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public File getFileByNumber(int ref) { + if (ref < 0 || ref >= 10000 || files == null) { + throw new IllegalAddressException(); + } + + synchronized (files) { + for (File file : files) { + if (file.getFileNumber() == ref) { + return file; + } + } + } + + throw new IllegalAddressException(); + } + + public int getFileCount() { + return files.size(); + } + + public FIFO getFIFO(int fifoNumber) { + try { + FIFO result = fifos.elementAt(fifoNumber); + if (result == null) { + throw new IllegalAddressException(); + } + + return result; + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + + public FIFO getFIFOByAddress(int ref) { + for (FIFO fifo : fifos) { + if (fifo.getAddress() == ref) { + return fifo; + } + } + + return null; + } + + public int getFIFOCount() { + if (fifos == null) { + return 0; + } + + return fifos.size(); + } + + public void setDigitalOut(int ref, DigitalOut _do) throws IllegalAddressException { + if (!isLocked()) { + try { + if (digitalOuts.get(ref) == null) { + throw new IllegalAddressException(); + } + digitalOuts.setElementAt(_do, ref); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addDigitalOut(DigitalOut _do) { + if (!isLocked()) { + digitalOuts.addElement(_do); + } + } + + public void addDigitalOut(int ref, DigitalOut dout) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (digitalOuts) { + if (ref < digitalOuts.size()) { + digitalOuts.setElementAt(dout, ref); + return; + } + digitalOuts.setSize(ref + 1); + digitalOuts.setElementAt(dout, ref); + } + } + } + + public void removeDigitalOut(DigitalOut _do) { + if (!isLocked()) { + digitalOuts.removeElement(_do); + } + } + + public void setDigitalIn(int ref, DigitalIn di) throws IllegalAddressException { + if (!isLocked()) { + try { + if (digitalIns.get(ref) == null) { + throw new IllegalAddressException(); + } + digitalIns.setElementAt(di, ref); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addDigitalIn(DigitalIn di) { + if (!isLocked()) { + digitalIns.addElement(di); + } + } + + public void addDigitalIn(int ref, DigitalIn d1) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (digitalIns) { + if (ref < digitalIns.size()) { + digitalIns.setElementAt(d1, ref); + return; + } + digitalIns.setSize(ref + 1); + digitalIns.setElementAt(d1, ref); + } + } + } + + public void removeDigitalIn(DigitalIn di) { + if (!isLocked()) { + digitalIns.removeElement(di); + } + } + + public void setInputRegister(int ref, InputRegister reg) throws IllegalAddressException { + if (!isLocked()) { + try { + if (inputRegisters.get(ref) == null) { + throw new IllegalAddressException(); + } + + inputRegisters.setElementAt(reg, ref); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addInputRegister(InputRegister reg) { + if (!isLocked()) { + inputRegisters.addElement(reg); + } + } + + public void addInputRegister(int ref, InputRegister inReg) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (inputRegisters) { + if (ref < inputRegisters.size()) { + inputRegisters.setElementAt(inReg, ref); + return; + } + inputRegisters.setSize(ref + 1); + inputRegisters.setElementAt(inReg, ref); + } + } + } + + public void removeInputRegister(InputRegister reg) { + if (!isLocked()) { + inputRegisters.removeElement(reg); + } + } + + public void setRegister(int ref, Register reg) throws IllegalAddressException { + if (!isLocked()) { + try { + if (registers.get(ref) == null) { + throw new IllegalAddressException(); + } + + registers.setElementAt(reg, ref); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addRegister(Register reg) { + if (!isLocked()) { + registers.addElement(reg); + } + } + + public void addRegister(int ref, Register reg) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (registers) { + if (ref < registers.size()) { + registers.setElementAt(reg, ref); + return; + } + registers.setSize(ref + 1); + registers.setElementAt(reg, ref); + } + } + } + + public void removeRegister(Register reg) { + if (!isLocked()) { + registers.removeElement(reg); + } + } + + public void setFile(int fileNumber, File file) { + if (!isLocked()) { + try { + if (files.get(fileNumber) == null) { + throw new IllegalAddressException(); + } + + files.setElementAt(file, fileNumber); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addFile(File newFile) { + if (!isLocked()) { + files.add(newFile); + } + } + + public void addFile(int ref, File newFile) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (files) { + if (ref < files.size()) { + files.setElementAt(newFile, ref); + return; + } + files.setSize(ref + 1); + files.setElementAt(newFile, ref); + } + } + } + + public void removeFile(File oldFile) { + if (!isLocked()) { + files.removeElement(oldFile); + } + } + + public void setFIFO(int fifoNumber, FIFO fifo) { + if (!isLocked()) { + try { + if (fifos.get(fifoNumber) == null) { + throw new IllegalAddressException(); + } + + fifos.setElementAt(fifo, fifoNumber); + } + catch (IndexOutOfBoundsException ex) { + throw new IllegalAddressException(); + } + } + } + + public void addFIFO(FIFO fifo) { + if (!isLocked()) { + fifos.add(fifo); + } + } + + public void addFIFO(int ref, FIFO newFIFO) { + if (ref < 0 || ref >= 65536) { + throw new IllegalArgumentException(); + } + + if (!isLocked()) { + synchronized (fifos) { + if (ref < fifos.size()) { + fifos.setElementAt(newFIFO, ref); + return; + } + fifos.setSize(ref + 1); + fifos.setElementAt(newFIFO, ref); + } + } + } + + public void removeFIFO(FIFO oldFIFO) { + if (!isLocked()) { + fifos.removeElement(oldFIFO); + } + } + +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleRegister.java b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleRegister.java new file mode 100644 index 0000000..91e6df8 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/procimg/SimpleRegister.java @@ -0,0 +1,70 @@ +/* + * 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 Register. + *

+ * 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 processImages = new HashMap(); + + /** + * Creates a TCP modbus slave + * + * @param port Port to listen on if IP type + * @param poolSize Pool size for TCP slaves + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @throws ModbusException If a problem occurs e.g. port already in use + */ + protected ModbusSlave(int port, int poolSize, boolean useRtuOverTcp) throws ModbusException { + this(ModbusSlaveType.TCP, null, port, poolSize, null, useRtuOverTcp); + } + + /** + * Creates a TCP modbus slave + * + * @param address IP address to listen on + * @param port Port to listen on if IP type + * @param poolSize Pool size for TCP slaves + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @throws ModbusException If a problem occurs e.g. port already in use + */ + protected ModbusSlave(InetAddress address, int port, int poolSize, boolean useRtuOverTcp) throws ModbusException { + this(ModbusSlaveType.TCP, address, port, poolSize, null, useRtuOverTcp); + } + + /** + * Creates a UDP modbus slave + * + * @param port Port to listen on if IP type + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @throws ModbusException If a problem occurs e.g. port already in use + */ + protected ModbusSlave(int port, boolean useRtuOverTcp) throws ModbusException { + this(ModbusSlaveType.UDP, null, port, 0, null, useRtuOverTcp); + } + + /** + * Creates a UDP modbus slave + * + * @param address IP address to listen on + * @param port Port to listen on if IP type + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @throws ModbusException If a problem occurs e.g. port already in use + */ + protected ModbusSlave(InetAddress address, int port, boolean useRtuOverTcp) throws ModbusException { + this(ModbusSlaveType.UDP, address, port, 0, null, useRtuOverTcp); + } + + /** + * Creates a serial modbus slave + * + * @param serialParams Serial parameters for serial type slaves + * @throws ModbusException If a problem occurs e.g. port already in use + */ + protected ModbusSlave(SerialParameters serialParams) throws ModbusException { + this(ModbusSlaveType.SERIAL, null, 0, 0, serialParams, false); + } + + /** + * Creates an appropriate type of listener + * + * @param type Type of slave to create + * @param address IP address to listen on + * @param port Port to listen on if IP type + * @param poolSize Pool size for TCP slaves + * @param serialParams Serial parameters for serial type slaves + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @throws ModbusException If a problem occurs e.g. port already in use + */ + private ModbusSlave(ModbusSlaveType type, InetAddress address, int port, int poolSize, SerialParameters serialParams, boolean useRtuOverTcp) throws ModbusException { + this.type = type == null ? ModbusSlaveType.TCP : type; + this.port = port; + this.serialParams = serialParams; + + // Create the listener + + logger.debug("Creating {} listener", this.type.toString()); + if (this.type.is(ModbusSlaveType.UDP)) { + listener = new ModbusUDPListener(); + } + else if (this.type.is(ModbusSlaveType.TCP)) { + listener = new ModbusTCPListener(poolSize, useRtuOverTcp); + } + else { + // listener = new ModbusSerialListener(serialParams); + } + + listener.setListening(true); + listener.setAddress(address); + listener.setPort(port); + listener.setTimeout(0); + } + + /** + * Returns the type of this slave + * + * @return Type of slave + */ + public ModbusSlaveType getType() { + return type; + } + + /** + * Returns the port that this IP slave is listening on + * + * @return Port being listened on if TCP or UDP + */ + public int getPort() { + return port; + } + + /** + * Returns the process image for the given Unit ID + * + * @param unitId Unit ID of the associated image + * @return Process image + */ + public ProcessImage getProcessImage(int unitId) { + return processImages.get(unitId); + } + + /** + * Removes the process image for the given Unit ID + * + * @param unitId Unit ID of the associated image + * @return Process image + */ + public ProcessImage removeProcessImage(int unitId) { + return processImages.remove(unitId); + } + + /** + * Adds a process image for the given Unit ID + * + * @param unitId Unit ID to associate with this image + * @param processImage Process image to add + * @return Process image + */ + public ProcessImage addProcessImage(int unitId, ProcessImage processImage) { + return processImages.put(unitId, processImage); + } + + /** + * Returns the serial parameters of this slave if it is a Serial type + * + * @return Serial parameters + */ + public SerialParameters getSerialParams() { + return serialParams; + } + + /** + * Opens the listener to service requests + * + * @throws ModbusException If we cannot listen + */ + public void open() throws ModbusException { + + // Start the listener if it isn' already running + + if (!isRunning) { + try { + listenerThread = new Thread(listener); + listenerThread.start(); + isRunning = true; + } + catch (Exception x) { + closeListener(); + throw new ModbusException(x.getMessage()); + } + } + } + + /** + * Convenience method for closing this port and removing it from the running list - simply + * calls ModbusSlaveFactory.close(this) + */ + public void close() { + ModbusSlaveFactory.close(this); + } + + /** + * Returns the last error accrued by the listener + * + * @return Error if there is one + */ + public String getError() { + return listener != null ? listener.getError() : null; + } + + /** + * Returns the listener used for this port + * + * @return Listener + */ + protected AbstractModbusListener getListener() { + return listener; + } + + /** + * Closes the listener of this slave + */ + @SuppressWarnings("deprecation") + void closeListener() { + if (listener != null && listener.isListening()) { + listener.stop(); + + // Wait until the listener says it has stopped, but don't wait forever + int count = 0; + while (listenerThread != null && listenerThread.isAlive() && count < 50) { + ModbusUtil.sleep(100); + count++; + } + // If the listener is still not stopped, kill the thread + if (listenerThread != null && listenerThread.isAlive()) { + listenerThread.stop(); + } + listenerThread = null; + } + isRunning = false; + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.java new file mode 100644 index 0000000..d8ad31a --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveFactory.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.slave; + +import com.ghgande.j2mod.modbus.ModbusException; +import com.ghgande.j2mod.modbus.net.AbstractModbusListener; +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.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * This is a factory class that allows users to easily create and manage slaves.
+ * 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 slaves = new HashMap(); + + /** + * Creates a TCP modbus slave or returns the one already allocated to this port + * + * @param port Port to listen on + * @param poolSize Pool size of listener threads + * @return new or existing TCP modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createTCPSlave(int port, int poolSize) throws ModbusException { + return createTCPSlave(port, poolSize, false); + } + + /** + * Creates a TCP modbus slave or returns the one already allocated to this port + * + * @param port Port to listen on + * @param poolSize Pool size of listener threads + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @return new or existing TCP modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createTCPSlave(int port, int poolSize, boolean useRtuOverTcp) throws ModbusException { + return ModbusSlaveFactory.createTCPSlave(null, port, poolSize, useRtuOverTcp); + } + + /** + * Creates a TCP modbus slave or returns the one already allocated to this port + * + * @param address IP address to listen on + * @param port Port to listen on + * @param poolSize Pool size of listener threads + * @param useRtuOverTcp True if the RTU protocol should be used over TCP + * @return new or existing TCP modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createTCPSlave(InetAddress address, int port, int poolSize, boolean useRtuOverTcp) throws ModbusException { + String key = ModbusSlaveType.TCP.getKey(port); + if (slaves.containsKey(key)) { + return slaves.get(key); + } + else { + ModbusSlave slave = new ModbusSlave(address, port, poolSize, useRtuOverTcp); + slaves.put(key, slave); + return slave; + } + } + + /** + * Creates a UDP modbus slave or returns the one already allocated to this port + * + * @param port Port to listen on + * @return new or existing UDP modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createUDPSlave(int port) throws ModbusException { + return createUDPSlave(null, port); + } + + /** + * Creates a UDP modbus slave or returns the one already allocated to this port + * + * @param address IP address to listen on + * @param port Port to listen on + * @return new or existing UDP modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createUDPSlave(InetAddress address, int port) throws ModbusException { + String key = ModbusSlaveType.UDP.getKey(port); + if (slaves.containsKey(key)) { + return slaves.get(key); + } + else { + ModbusSlave slave = new ModbusSlave(address, port, false); + slaves.put(key, slave); + return slave; + } + } + + /** + * Creates a serial modbus slave or returns the one already allocated to this port + * + * @param serialParams Serial parameters for serial type slaves + * @return new or existing Serial modbus slave associated with the port + * + * @throws ModbusException If a problem occurs e.g. port already in use + */ + public static synchronized ModbusSlave createSerialSlave(SerialParameters serialParams) throws ModbusException { + ModbusSlave slave = null; + if (serialParams == null) { + throw new ModbusException("Serial parameters are null"); + } + else if (ModbusUtil.isBlank(serialParams.getPortName())) { + throw new ModbusException("Serial port name is empty"); + } + + // If we have a slave already assigned to this port + if (slaves.containsKey(serialParams.getPortName())) { + slave = slaves.get(serialParams.getPortName()); + + // Check if any of the parameters have changed + if (!serialParams.toString().equals(slave.getSerialParams().toString())) { + close(slave); + slave = null; + } + } + + // If we don;t have a slave, create one + if (slave == null) { + slave = new ModbusSlave(serialParams); + slaves.put(serialParams.getPortName(), slave); + return slave; + } + return slave; + } + + /** + * Closes this slave and removes it from the running list + * + * @param slave Slave to remove + */ + public static synchronized void close(ModbusSlave slave) { + if (slave != null) { + slave.closeListener(); + slaves.remove(slave.getType().getKey(slave.getPort())); + } + } + + /** + * Closes all slaves and removes them from the running list + */ + public static synchronized void close() { + for (ModbusSlave slave : new ArrayList(slaves.values())) { + slave.close(); + } + } + + /** + * Returns the running slave listening on the given IP port + * + * @param port Port to check for running slave + * @return Null or ModbusSlave + */ + public static ModbusSlave getSlave(int port) { + return slaves.get(port + ""); + } + + /** + * Returns the running slave listening on the given serial port + * + * @param port Port to check for running slave + * @return Null or ModbusSlave + */ + public static ModbusSlave getSlave(String port) { + return ModbusUtil.isBlank(port) ? null : slaves.get(port); + } + + /** + * Returns the running slave that utilises the give listener + * + * @param listener Listener used for this slave + * @return Null or ModbusSlave + */ + public static synchronized ModbusSlave getSlave(AbstractModbusListener listener) { + for (ModbusSlave slave : slaves.values()) { + if (slave.getListener().equals(listener)) { + return slave; + } + } + return null; + } + +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveType.java b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveType.java new file mode 100644 index 0000000..c993d15 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/slave/ModbusSlaveType.java @@ -0,0 +1,52 @@ +/* + * 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.util.ModbusUtil; + +/** + * Descibes the types of Modbus Slaves + */ +public enum ModbusSlaveType { + TCP, UDP, SERIAL; + + /** + * Returns true if this type is one of those listed + * + * @param types Array of types to check for + * @return True if this is one of the array + */ + public boolean is(ModbusSlaveType... types) { + if (!ModbusUtil.isBlank(types)) { + for (ModbusSlaveType type : types) { + if (equals(type)) { + return true; + } + } + } + return false; + } + + /** + * Returns a unique key for this port and type + * + * @param port Port number + * @return Unique key + */ + public String getKey(int port) { + return toString() + port; + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/BitVector.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/BitVector.java new file mode 100644 index 0000000..ff82cf9 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/BitVector.java @@ -0,0 +1,359 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that implements a collection for + * bits, storing them packed into bytes. + * Per default the access operations will index from + * the LSB (rightmost) bit. + * + * @author Dieter Wimberger + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public final class BitVector { + + private static final Logger logger = LoggerFactory.getLogger(BitVector.class); + private static final int[] ODD_OFFSETS = {-1, -3, -5, -7}; + private static final int[] STRAIGHT_OFFSETS = {7, 5, 3, 1}; + //instance attributes + private int size; + private byte[] data; + private boolean MSBAccess = false; + + /** + * Constructs a new BitVector instance + * with a given size. + *

+ * + * @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[].
+ * 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: + * + *

(((a & 0xff) << 8) | (b & 0xff))
+ * + * 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: + * + *

+     * (byte)(0xff & (v >> 8))
+     * (byte)(0xff & v)
+     * 
+ * + * 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: + * + *

+     * (short)((a << 8) | (b & 0xff))
+     * 
+ * + * 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: + * + *

+     * (byte)(0xff & (v >> 8))
+     * (byte)(0xff & v)
+     * 
+ * + * @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.
+ * The value returned is: + * + *

+     * (((a & 0xff) << 24) | ((b & 0xff) << 16) |
+     *  ((c & 0xff) << 8) | (d & 0xff))
+     * 
+ * + * @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 list) { + return list == null || list.isEmpty(); + } + + /** + * Return true if the array is null or empty + * + * @param list Array to check + * @return True if the array is blank or empty + */ + public static boolean isBlank(Object[] list) { + return list == null || list.length == 0; + } + + /** + * Sleeps safely for the specified amount of time unless awoken by an interruption + * + * @param time Time in milliseconds + */ + public static void sleep(long time) { + try { + Thread.sleep(time); + } + catch (InterruptedException ex) { + logger.warn("Backout sleep timer has been interrupted"); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/Observable.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/Observable.java new file mode 100644 index 0000000..a7861ff --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/Observable.java @@ -0,0 +1,88 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Vector; + +/** + * A cleanroom implementation of the Observable pattern. + * + * @author Dieter Wimberger (wimpi) + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public class Observable { + + private static final Logger logger = LoggerFactory.getLogger(Observable.class); + + private Vector observers; + + /** + * Constructs a new Observable instance. + */ + public Observable() { + observers = new Vector(10); + } + + public synchronized int getObserverCount() { + return observers.size(); + } + + /** + * Adds an observer instance if it is not already in the set of observers + * for this Observable. + * + * @param o an observer instance to be added. + */ + public synchronized void addObserver(Observer o) { + if (!observers.contains(o)) { + observers.addElement(o); + } + } + + /** + * Removes an observer instance from the set of observers of this + * Observable. + * + * @param o an observer instance to be removed. + */ + public synchronized void removeObserver(Observer o) { + observers.removeElement(o); + } + + /** + * Removes all observer instances from the set of observers of this + * Observable. + */ + public synchronized void removeObservers() { + observers.removeAllElements(); + } + + /** + * Notifies all observer instances in the set of observers of this + * Observable. + * + * @param arg an arbitrary argument to be passed. + */ + public synchronized void notifyObservers(Object arg) { + for (int i = 0; i < observers.size(); i++) { + observers.elementAt(i).update(this, arg); + } + } +} diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/Observer.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/Observer.java new file mode 100644 index 0000000..46d0466 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/Observer.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * A cleanroom implementation of the Observer interface + * for the Observable design pattern. + * + * @author Dieter Wimberger (wimpi) + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public interface Observer { + + /** + * Updates the state of this Observer to be in + * synch with an Observable instance. + * The argument should usually be an indication of the + * aspects that changed in the Observable. + * + * @param o an Observable instance. + * @param arg an arbitrary argument to be passed. + */ + void update(Observable o, Object arg); + +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java new file mode 100644 index 0000000..80c9011 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/SerialParameters.java @@ -0,0 +1,557 @@ +/* + * 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.net.AbstractSerialConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Properties; + +/** + * Helper class wrapping all serial port communication parameters. + * Very similar to the javax.comm demos, however, not the same. + * + * @author Dieter Wimberger + * @author John Charlton + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public class SerialParameters { + + private static final Logger logger = LoggerFactory.getLogger(SerialParameters.class); + + //instance attributes + private String portName; + private int baudRate; + private int flowControlIn; + private int flowControlOut; + private int databits; + private int stopbits; + private int parity; + private String encoding; + private boolean echo; + private int openDelay; + + /** + * Constructs a new SerialParameters instance with + * default values. + */ + public SerialParameters() { + portName = ""; + baudRate = 9600; + flowControlIn = AbstractSerialConnection.FLOW_CONTROL_DISABLED; + flowControlOut = AbstractSerialConnection.FLOW_CONTROL_DISABLED; + databits = 8; + stopbits = AbstractSerialConnection.ONE_STOP_BIT; + parity = AbstractSerialConnection.NO_PARITY; + encoding = Modbus.DEFAULT_SERIAL_ENCODING; + echo = false; + openDelay = AbstractSerialConnection.OPEN_DELAY; + } + + /** + * Constructs a new SerialParameters instance with + * given parameters. + * + * @param portName The name of the port. + * @param baudRate The baud rate. + * @param flowControlIn Type of flow control for receiving. + * @param flowControlOut Type of flow control for sending. + * @param databits The number of data bits. + * @param stopbits The number of stop bits. + * @param parity The type of parity. + * @param echo Flag for setting the RS485 echo mode. + */ + public SerialParameters(String portName, int baudRate, + int flowControlIn, + int flowControlOut, + int databits, + int stopbits, + int parity, + boolean echo) { + this.portName = portName; + this.baudRate = baudRate; + this.flowControlIn = flowControlIn; + this.flowControlOut = flowControlOut; + this.databits = databits; + this.stopbits = stopbits; + this.parity = parity; + this.echo = echo; + } + + /** + * Constructs a new SerialParameters instance with + * parameters obtained from a Properties instance. + * + * @param props a Properties instance. + * @param prefix a prefix for the properties keys if embedded into + * other properties. + */ + public SerialParameters(Properties props, String prefix) { + if (prefix == null) { + prefix = ""; + } + setPortName(props.getProperty(prefix + "portName", "")); + setBaudRate(props.getProperty(prefix + "baudRate", "" + 9600)); + setFlowControlIn(props.getProperty(prefix + "flowControlIn", "" + AbstractSerialConnection.FLOW_CONTROL_DISABLED)); + setFlowControlOut(props.getProperty(prefix + "flowControlOut", "" + AbstractSerialConnection.FLOW_CONTROL_DISABLED)); + setParity(props.getProperty(prefix + "parity", "" + AbstractSerialConnection.NO_PARITY)); + setDatabits(props.getProperty(prefix + "databits", "8")); + setStopbits(props.getProperty(prefix + "stopbits", "" + AbstractSerialConnection.ONE_STOP_BIT)); + setEncoding(props.getProperty(prefix + "encoding", Modbus.DEFAULT_SERIAL_ENCODING)); + setEcho("true".equals(props.getProperty(prefix + "echo"))); + setOpenDelay(props.getProperty(prefix + "openDelay", "" + AbstractSerialConnection.OPEN_DELAY)); + } + + /** + * Returns the port name. + * + * @return the port name. + */ + public String getPortName() { + return portName; + } + + /** + * Sets the port name. + * + * @param name the new port name. + */ + public void setPortName(String name) { + portName = name; + } + + /** + * Sets the baud rate. + * + * @param rate the new baud rate. + */ + public void setBaudRate(int rate) { + baudRate = rate; + } + + /** + * Return the baud rate as int. + * + * @return the baud rate as int. + */ + public int getBaudRate() { + return baudRate; + } + + /** + * Sets the baud rate. + * + * @param rate the new baud rate. + */ + public void setBaudRate(String rate) { + baudRate = Integer.parseInt(rate); + } + + /** + * Returns the baud rate as a String. + * + * @return the baud rate as String. + */ + public String getBaudRateString() { + return Integer.toString(baudRate); + } + + /** + * Sets the type of flow control for the input + * as given by the passed in int. + * + * @param flowcontrol the new flow control type. + */ + public void setFlowControlIn(int flowcontrol) { + flowControlIn = flowcontrol; + } + + /** + * Returns the input flow control type as int. + * + * @return the input flow control type as int. + */ + public int getFlowControlIn() { + return flowControlIn; + } + + /** + * Sets the type of flow control for the input + * as given by the passed in String. + * + * @param flowcontrol the flow control for reading type. + */ + public void setFlowControlIn(String flowcontrol) { + flowControlIn = stringToFlow(flowcontrol); + } + + /** + * Returns the input flow control type as String. + * + * @return the input flow control type as String. + */ + public String getFlowControlInString() { + return flowToString(flowControlIn); + } + + /** + * Sets the output flow control type as given + * by the passed in int. + * + * @param flowControlOut new output flow control type as int. + */ + public void setFlowControlOut(int flowControlOut) { + this.flowControlOut = flowControlOut; + } + + /** + * Returns the output flow control type as int. + * + * @return the output flow control type as int. + */ + public int getFlowControlOut() { + return flowControlOut; + } + + /** + * Sets the output flow control type as given + * by the passed in String. + * + * @param flowControlOut the new output flow control type as String. + */ + public void setFlowControlOut(String flowControlOut) { + this.flowControlOut = stringToFlow(flowControlOut); + } + + /** + * Returns the output flow control type as String. + * + * @return the output flow control type as String. + */ + public String getFlowControlOutString() { + return flowToString(flowControlOut); + } + + /** + * Sets the number of data bits. + * + * @param databits the new number of data bits. + */ + public void setDatabits(int databits) { + this.databits = databits; + } + + /** + * Returns the number of data bits as int. + * + * @return the number of data bits as int. + */ + public int getDatabits() { + return databits; + } + + /** + * Sets the number of data bits from the given String. + * + * @param databits the new number of data bits as String. + */ + public void setDatabits(String databits) { + if (!ModbusUtil.isBlank(databits) && databits.matches("[0-9]+")) { + this.databits = Integer.parseInt(databits); + } + else { + this.databits = 8; + } + } + + /** + * Returns the number of data bits as String. + * + * @return the number of data bits as String. + */ + public String getDatabitsString() { + return databits + ""; + } + + /** + * Sets the number of stop bits. + * + * @param stopbits the new number of stop bits setting. + */ + public void setStopbits(int stopbits) { + this.stopbits = stopbits; + } + + /** + * Returns the number of stop bits as int. + * + * @return the number of stop bits as int. + */ + public int getStopbits() { + return stopbits; + } + + /** + * Sets the number of stop bits from the given String. + * + * @param stopbits the number of stop bits as String. + */ + public void setStopbits(String stopbits) { + if (ModbusUtil.isBlank(stopbits) || stopbits.equals("1")) { + this.stopbits = AbstractSerialConnection.ONE_STOP_BIT; + } + else if (stopbits.equals("1.5")) { + this.stopbits = AbstractSerialConnection.ONE_POINT_FIVE_STOP_BITS; + } + else if (stopbits.equals("2")) { + this.stopbits = AbstractSerialConnection.TWO_STOP_BITS; + } + } + + /** + * Returns the number of stop bits as String. + * + * @return the number of stop bits as String. + */ + public String getStopbitsString() { + switch (stopbits) { + case AbstractSerialConnection.ONE_STOP_BIT: + return "1"; + case AbstractSerialConnection.ONE_POINT_FIVE_STOP_BITS: + return "1.5"; + case AbstractSerialConnection.TWO_STOP_BITS: + return "2"; + default: + return "1"; + } + } + + /** + * Sets the parity schema. + * + * @param parity the new parity schema as int. + */ + public void setParity(int parity) { + this.parity = parity; + } + + /** + * Returns the parity schema as int. + * + * @return the parity schema as int. + */ + public int getParity() { + return parity; + } + + /** + * Sets the parity schema from the given + * String. + * + * @param parity the new parity schema as String. + */ + public void setParity(String parity) { + if (ModbusUtil.isBlank(parity) || parity.equalsIgnoreCase("none")) { + this.parity = AbstractSerialConnection.NO_PARITY; + } + else if (parity.equalsIgnoreCase("even")) { + this.parity = AbstractSerialConnection.EVEN_PARITY; + } + else if (parity.equalsIgnoreCase("odd")) { + this.parity = AbstractSerialConnection.ODD_PARITY; + } + else if (parity.equalsIgnoreCase("mark")) { + this.parity = AbstractSerialConnection.MARK_PARITY; + } + else if (parity.equalsIgnoreCase("space")) { + this.parity = AbstractSerialConnection.SPACE_PARITY; + } + else { + this.parity = AbstractSerialConnection.NO_PARITY; + } + } + + /** + * Returns the parity schema as String. + * + * @return the parity schema as String. + */ + public String getParityString() { + switch (parity) { + case AbstractSerialConnection.NO_PARITY: + return "none"; + case AbstractSerialConnection.EVEN_PARITY: + return "even"; + case AbstractSerialConnection.ODD_PARITY: + return "odd"; + case AbstractSerialConnection.MARK_PARITY: + return "mark"; + case AbstractSerialConnection.SPACE_PARITY: + return "space"; + default: + return "none"; + } + } + + /** + * Returns the encoding to be used. + * + * @return the encoding as string. + * + * @see Modbus#SERIAL_ENCODING_ASCII + * @see Modbus#SERIAL_ENCODING_RTU + */ + public String getEncoding() { + return encoding; + } + + /** + * Sets the encoding to be used. + * + * @param enc the encoding as string. + * @see Modbus#SERIAL_ENCODING_ASCII + * @see Modbus#SERIAL_ENCODING_RTU + */ + public void setEncoding(String enc) { + if (!ModbusUtil.isBlank(enc) && + (enc.equalsIgnoreCase(Modbus.SERIAL_ENCODING_ASCII) || enc.equalsIgnoreCase(Modbus.SERIAL_ENCODING_RTU))) { + encoding = enc; + } + else { + encoding = Modbus.DEFAULT_SERIAL_ENCODING; + } + } + + /** + * Get the Echo value. + * + * @return the Echo value. + */ + public boolean isEcho() { + return echo; + } + + /** + * Set the Echo value. + * + * @param newEcho The new Echo value. + */ + public void setEcho(boolean newEcho) { + echo = newEcho; + } + + /** + * Converts a String describing a flow control type to the + * int which is defined in SerialPort. + * + * @param flowcontrol the String describing the flow control type. + * @return the int describing the flow control type. + */ + private int stringToFlow(String flowcontrol) { + if (ModbusUtil.isBlank(flowcontrol) || flowcontrol.equalsIgnoreCase("none")) { + return AbstractSerialConnection.FLOW_CONTROL_DISABLED; + } + else if (flowcontrol.equalsIgnoreCase("xon/xoff out")) { + return AbstractSerialConnection.FLOW_CONTROL_XONXOFF_OUT_ENABLED; + } + else if (flowcontrol.equalsIgnoreCase("xon/xoff in")) { + return AbstractSerialConnection.FLOW_CONTROL_XONXOFF_IN_ENABLED; + } + else if (flowcontrol.equalsIgnoreCase("rts/cts")) { + return AbstractSerialConnection.FLOW_CONTROL_CTS_ENABLED | AbstractSerialConnection.FLOW_CONTROL_RTS_ENABLED; + } + else if (flowcontrol.equalsIgnoreCase("dsr/dtr")) { + return AbstractSerialConnection.FLOW_CONTROL_DSR_ENABLED | AbstractSerialConnection.FLOW_CONTROL_DTR_ENABLED; + } + return AbstractSerialConnection.FLOW_CONTROL_DISABLED; + } + + /** + * Converts an int describing a flow control type to a + * String describing a flow control type. + * + * @param flowcontrol the int describing the + * flow control type. + * @return the String describing the flow control type. + */ + private String flowToString(int flowcontrol) { + switch (flowcontrol) { + case AbstractSerialConnection.FLOW_CONTROL_DISABLED: + return "none"; + case AbstractSerialConnection.FLOW_CONTROL_XONXOFF_OUT_ENABLED: + return "xon/xoff out"; + case AbstractSerialConnection.FLOW_CONTROL_XONXOFF_IN_ENABLED: + return "xon/xoff in"; + case AbstractSerialConnection.FLOW_CONTROL_CTS_ENABLED: + return "rts/cts"; + case AbstractSerialConnection.FLOW_CONTROL_DTR_ENABLED: + return "dsr/dtr"; + default: + return "none"; + } + } + + /** + * Gets the open delay used to prevent some OS from losing the comms port + * + * @return Sleep before an open is attempted on a comms port + */ + public int getOpenDelay() { + return openDelay; + } + + /** + * Sets the sleep time tat occurs just prior to opening a coms port + * Some OS don't like to have their comms ports opened/closed in very quick succession + * particularly, virtual ports. This delay is a rather crude way of stopping the problem that + * a comms port doesn't re-appear immediately after a close + * + * @param openDelay Sleep time in millieseconds + */ + public void setOpenDelay(int openDelay) { + this.openDelay = openDelay; + } + + /** + * Sets the sleep time tat occurs just prior to opening a coms port + * Some OS don't like to have their comms ports opened/closed in very quick succession + * particularly, virtual ports. This delay is a rather crude way of stopping the problem that + * a comms port doesn't re-appear immediately after a close + * + * @param openDelay Sleep time in millieseconds + */ + public void setOpenDelay(String openDelay) { + this.openDelay = Integer.parseInt(openDelay); + } + + @Override + public String toString() { + return "SerialParameters{" + + "portName='" + portName + '\'' + + ", baudRate=" + baudRate + + ", flowControlIn=" + flowControlIn + + ", flowControlOut=" + flowControlOut + + ", databits=" + databits + + ", stopbits=" + stopbits + + ", parity=" + parity + + ", encoding='" + encoding + '\'' + + ", echo=" + echo + + ", openDelay=" + openDelay + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ghgande/j2mod/modbus/util/ThreadPool.java b/app/src/main/java/com/ghgande/j2mod/modbus/util/ThreadPool.java new file mode 100644 index 0000000..11055a0 --- /dev/null +++ b/app/src/main/java/com/ghgande/j2mod/modbus/util/ThreadPool.java @@ -0,0 +1,126 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Class implementing a simple thread pool. + * + * @author Dieter Wimberger + * @author Steve O'Hara (4NG) + * @version 2.0 (March 2016) + */ +public class ThreadPool { + + private static final Logger logger = LoggerFactory.getLogger(ThreadPool.class); + + private LinkedBlockingQueue taskPool; + private List threadPool = new ArrayList(); + private int size = 1; + private boolean running; + + /** + * Constructs a new ThreadPool instance. + * + * @param size the size of the thread pool. + */ + public ThreadPool(int size) { + this.size = size; + taskPool = new LinkedBlockingQueue(); + initPool(); + } + + /** + * Execute the Runnable instance + * through a thread in this ThreadPool. + * + * @param task the Runnable to be executed. + */ + public synchronized void execute(Runnable task) { + if (running) { + try { + taskPool.put(task); + } + catch (InterruptedException ex) { + //FIXME: Handle!? + } + } + } + + /** + * Initializes the pool, populating it with + * n started threads. + */ + protected void initPool() { + running = true; + for (int i = size; --i >= 0; ) { + PoolThread thread = new PoolThread(); + threadPool.add(thread); + thread.start(); + } + } + + /** + * Shutdown the pool of threads + */ + public void close() { + if (running) { + taskPool.clear(); + running = false; + for (PoolThread thread : threadPool) { + thread.interrupt(); + } + } + } + + /** + * Inner class implementing a thread that can be + * run in a ThreadPool. + * + * @author Dieter Wimberger + * @version 1.2rc1 (09/11/2004) + */ + private class PoolThread extends Thread { + + /** + * Runs the PoolThread. + *

+ * This method will infinitely loop, picking + * up available tasks from the LinkedQueue. + */ + public void run() { + logger.debug("Running PoolThread"); + do { + try { + logger.debug(this.toString()); + taskPool.take().run(); + } + catch (Exception ex) { + if (running) { + logger.error("Problem starting receiver thread", ex); + } + } + } while (running); + } + } + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_display_message.xml b/app/src/main/res/layout/activity_display_message.xml new file mode 100644 index 0000000..583d364 --- /dev/null +++ b/app/src/main/res/layout/activity_display_message.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..01ebacd --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,74 @@ + + + + + +