initial commit
This commit is contained in:
commit
2046758cc4
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
BIN
.idea/caches/build_file_checksums.ser
Normal file
BIN
.idea/caches/build_file_checksums.ser
Normal file
Binary file not shown.
29
.idea/codeStyles/Project.xml
Normal file
29
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
</code_scheme>
|
||||
</component>
|
18
.idea/gradle.xml
Normal file
18
.idea/gradle.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
34
.idea/misc.xml
Normal file
34
.idea/misc.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="5">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
Normal file
12
.idea/runConfigurations.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
30
app/build.gradle
Normal file
30
app/build.gradle
Normal file
@ -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'
|
||||
}
|
21
app/proguard-rules.pro
vendored
Normal file
21
app/proguard-rules.pro
vendored
Normal file
@ -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
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
40
app/src/main/AndroidManifest.xml
Normal file
40
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.user.myapp">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".GlobalState"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".DisplayMessageActivity"
|
||||
android:parentActivityName=".MainActivity">
|
||||
|
||||
<!-- The meta-data tag is required if you support API level 15 and lower -->
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".MyIntentService"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name=".MeasurementActivity" />
|
||||
<activity android:name=".ManualDriveActivity"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -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());
|
||||
}
|
||||
};
|
||||
}
|
51
app/src/main/java/com/example/user/myapp/GlobalState.java
Normal file
51
app/src/main/java/com/example/user/myapp/GlobalState.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
52
app/src/main/java/com/example/user/myapp/MainActivity.java
Normal file
52
app/src/main/java/com/example/user/myapp/MainActivity.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
115
app/src/main/java/com/example/user/myapp/MyIntentService.java
Normal file
115
app/src/main/java/com/example/user/myapp/MyIntentService.java
Normal file
@ -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.
|
||||
* <p>
|
||||
* 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<Boolean> 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<Boolean> 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<Boolean> booleanList) {
|
||||
final boolean[] primitives = new boolean[booleanList.size()];
|
||||
int index = 0;
|
||||
for (Boolean object : booleanList) {
|
||||
primitives[index++] = object;
|
||||
}
|
||||
return primitives;
|
||||
}
|
||||
}
|
89
app/src/main/java/com/example/user/myapp/MyTest.java
Normal file
89
app/src/main/java/com/example/user/myapp/MyTest.java
Normal file
@ -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<String, Void, String> {
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
349
app/src/main/java/com/ghgande/j2mod/modbus/Modbus.java
Normal file
349
app/src/main/java/com/ghgande/j2mod/modbus/Modbus.java
Normal file
@ -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 <tt>read coils</tt>.
|
||||
*/
|
||||
int READ_COILS = 1;
|
||||
|
||||
/**
|
||||
* Defines a class 1 function code
|
||||
* for <tt>read input discretes</tt>.
|
||||
*/
|
||||
int READ_INPUT_DISCRETES = 2;
|
||||
|
||||
/**
|
||||
* Defines a class 1 function code
|
||||
* for <tt>read holding registers</tt>
|
||||
*/
|
||||
int READ_HOLDING_REGISTERS = 3;
|
||||
|
||||
/**
|
||||
* Defines the class 0 function code
|
||||
* for <tt>read multiple registers</tt>. The
|
||||
* proper name is "Read Holding Registers".
|
||||
*/
|
||||
int READ_MULTIPLE_REGISTERS = 3;
|
||||
|
||||
/**
|
||||
* Defines a class 1 function code
|
||||
* for <tt>read input registers</tt>.
|
||||
*/
|
||||
int READ_INPUT_REGISTERS = 4;
|
||||
|
||||
/**
|
||||
* Defines a class 1 function code
|
||||
* for <tt>write coil</tt>.
|
||||
*/
|
||||
int WRITE_COIL = 5;
|
||||
|
||||
/**
|
||||
* Defines a class 1 function code
|
||||
* for <tt>write single register</tt>.
|
||||
*/
|
||||
int WRITE_SINGLE_REGISTER = 6;
|
||||
|
||||
/**
|
||||
* <tt>read exception status</tt>
|
||||
*
|
||||
* Serial devices only.
|
||||
*/
|
||||
int READ_EXCEPTION_STATUS = 7;
|
||||
|
||||
/**
|
||||
* <tt>get serial diagnostics</tt>
|
||||
*
|
||||
* Serial devices only.
|
||||
*/
|
||||
int READ_SERIAL_DIAGNOSTICS = 8;
|
||||
|
||||
/**
|
||||
* <tt>get comm event counter</tt>
|
||||
*
|
||||
* Serial devices only.
|
||||
*/
|
||||
int READ_COMM_EVENT_COUNTER = 11;
|
||||
|
||||
/**
|
||||
* <tt>get comm event log</tt>
|
||||
*
|
||||
* Serial devices only.
|
||||
*/
|
||||
int READ_COMM_EVENT_LOG = 12;
|
||||
|
||||
/**
|
||||
* Defines a standard function code
|
||||
* for <tt>write multiple coils</tt>.
|
||||
*/
|
||||
int WRITE_MULTIPLE_COILS = 15;
|
||||
|
||||
/**
|
||||
* Defines the class 0 function code
|
||||
* for <tt>write multiple registers</tt>.
|
||||
*/
|
||||
int WRITE_MULTIPLE_REGISTERS = 16;
|
||||
|
||||
/**
|
||||
* Defines a standard function code
|
||||
* for <tt>read slave ID</tt>.
|
||||
*/
|
||||
int REPORT_SLAVE_ID = 17;
|
||||
|
||||
/**
|
||||
* <tt>read file record</tt>
|
||||
*/
|
||||
int READ_FILE_RECORD = 20;
|
||||
|
||||
/**
|
||||
* <tt>write file record</tt>
|
||||
*/
|
||||
int WRITE_FILE_RECORD = 21;
|
||||
|
||||
/**
|
||||
* <tt>mask write register</tt>
|
||||
*
|
||||
* Update a single register using its current value and an AND
|
||||
* and OR mask.
|
||||
*/
|
||||
int MASK_WRITE_REGISTER = 22;
|
||||
|
||||
/**
|
||||
* <tt>read / write multiple registers</tt>
|
||||
*
|
||||
* Write some number of registers, then read some number of
|
||||
* potentially other registers back.
|
||||
*/
|
||||
int READ_WRITE_MULTIPLE = 23;
|
||||
|
||||
/**
|
||||
* <tt>read FIFO queue</tt>
|
||||
*
|
||||
* 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 <b>on</b>.
|
||||
*/
|
||||
int COIL_ON = (byte)255;
|
||||
|
||||
/**
|
||||
* Defines the byte representation of the coil state <b>pos</b>.
|
||||
*/
|
||||
int COIL_OFF = 0;
|
||||
|
||||
/**
|
||||
* Defines the word representation of the coil state <b>on</b>.
|
||||
*/
|
||||
byte[] COIL_ON_BYTES = {(byte)COIL_ON, (byte)COIL_OFF};
|
||||
|
||||
/**
|
||||
* Defines the word representation of the coil state <b>pos</b>.
|
||||
*/
|
||||
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 (<b>2000</b>).
|
||||
*/
|
||||
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 <tt>illegal function</tt>.
|
||||
* This exception code is returned if the slave:
|
||||
* <ul>
|
||||
* <li>does not implement the function code <b>or</b></li>
|
||||
* <li>is not in a state that allows it to process the function</li>
|
||||
* </ul>
|
||||
*/
|
||||
int ILLEGAL_FUNCTION_EXCEPTION = 1;
|
||||
|
||||
/**
|
||||
* Defines the Modbus slave exception type <tt>illegal data address</tt>.
|
||||
* This exception code is returned if the reference:
|
||||
* <ul>
|
||||
* <li>does not exist on the slave <b>or</b></li>
|
||||
* <li>the combination of reference and length exceeds the bounds
|
||||
* of the existing registers.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
int ILLEGAL_ADDRESS_EXCEPTION = 2;
|
||||
|
||||
/**
|
||||
* Defines the Modbus slave exception type <tt>illegal data value</tt>.
|
||||
* This exception code indicates a fault in the structure of the data values
|
||||
* of a complex request, such as an incorrect implied length.<br>
|
||||
* <b>This code does not indicate a problem with application specific validity
|
||||
* of the value.</b>
|
||||
*/
|
||||
int ILLEGAL_VALUE_EXCEPTION = 3;
|
||||
|
||||
/**
|
||||
* Defines the Modbus slave exception type <tt>slave device failure</tt>.
|
||||
* This exception code indicates a fault in the slave device itself.
|
||||
*/
|
||||
int SLAVE_DEVICE_FAILURE = 4;
|
||||
|
||||
/**
|
||||
* Defines the Modbus slave exception type <tt>slave busy</tt>. 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 <tt>negative acknowledgment</tt>.
|
||||
* This exception code indicates the slave cannot perform the requested
|
||||
* action.
|
||||
*/
|
||||
int NEGATIVE_ACKNOWLEDGEMENT = 7;
|
||||
|
||||
/**
|
||||
* Defines the Modbus slave exception type <tt>Gateway target failed to
|
||||
* respond</tt>. 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
|
||||
* (=<tt>502</tt>).
|
||||
*/
|
||||
int DEFAULT_PORT = 502;
|
||||
|
||||
/**
|
||||
* Defines the maximum message length in bytes
|
||||
* (=<tt>256</tt>).
|
||||
*/
|
||||
int MAX_MESSAGE_LENGTH = 256;
|
||||
|
||||
/**
|
||||
* Defines the default transaction identifier (=<tt>0</tt>).
|
||||
*/
|
||||
int DEFAULT_TRANSACTION_ID = 0;
|
||||
|
||||
/**
|
||||
* Defines the default protocol identifier (=<tt>0</tt>).
|
||||
*/
|
||||
int DEFAULT_PROTOCOL_ID = 0;
|
||||
|
||||
/**
|
||||
* Defines the default unit identifier (=<tt>0</tt>).
|
||||
*/
|
||||
int DEFAULT_UNIT_ID = 0;
|
||||
|
||||
/**
|
||||
* Defines the default setting for validity checking
|
||||
* in transactions (=<tt>true</tt>).
|
||||
*/
|
||||
boolean DEFAULT_VALIDITYCHECK = true;
|
||||
|
||||
/**
|
||||
* Defines the default setting for I/O operation timeouts
|
||||
* in milliseconds (=<tt>3000</tt>).
|
||||
*/
|
||||
int DEFAULT_TIMEOUT = 3000;
|
||||
|
||||
/**
|
||||
* Defines the sleep period between transaction retries
|
||||
* in milliseconds (=<tt>200</tt>).
|
||||
*/
|
||||
int RETRY_SLEEP_TIME = 500;
|
||||
|
||||
/**
|
||||
* Defines the default reconnecting setting for
|
||||
* transactions (=<tt>false</tt>).
|
||||
*/
|
||||
boolean DEFAULT_RECONNECTING = false;
|
||||
|
||||
/**
|
||||
* Defines the default amount of retires for opening
|
||||
* a connection (=<tt>3</tt>).
|
||||
*/
|
||||
int DEFAULT_RETRIES = 5;
|
||||
|
||||
/**
|
||||
* Defines the default number of msec to delay before transmission<br>
|
||||
* 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
|
||||
* (=<tt>0</tt>).
|
||||
*/
|
||||
int DEFAULT_TRANSMIT_DELAY = 0;
|
||||
|
||||
/**
|
||||
* Defines the default number of msec to delay before transmission if not overridden by DEFAULT_TRANSMIT_DELAY
|
||||
* (=<tt>2</tt>).
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p><b>Note:</b> 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;
|
||||
|
||||
}
|
@ -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 <tt>ModbusException</tt> instance.
|
||||
*/
|
||||
public ModbusException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusException</tt> instance with the given
|
||||
* message.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusException</tt>.
|
||||
*/
|
||||
public ModbusException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusException</tt> instance with the given
|
||||
* message.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusException</tt>.
|
||||
* @param values optional values of the exception
|
||||
*/
|
||||
public ModbusException(String message, Object... values) {
|
||||
super(String.format(message, values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusException</tt> instance with the given
|
||||
* message and underlying cause.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusException</tt>.
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusIOException</tt>. 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 <tt>ModbusIOException</tt> instance.
|
||||
*/
|
||||
public ModbusIOException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusIOException</tt> instance with the given
|
||||
* message.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusIOException</tt>.
|
||||
*/
|
||||
public ModbusIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusIOException</tt> instance with the given
|
||||
* message.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusIOException</tt>.
|
||||
* @param values optional values of the exception
|
||||
*/
|
||||
public ModbusIOException(String message, Object... values) {
|
||||
super(message, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusIOException</tt> instance.
|
||||
*
|
||||
* @param b true if caused by end of stream, false otherwise.
|
||||
*/
|
||||
public ModbusIOException(boolean b) {
|
||||
eof = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusIOException</tt> instance with the given
|
||||
* message.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusIOException</tt>.
|
||||
* @param b true if caused by end of stream, false otherwise.
|
||||
*/
|
||||
public ModbusIOException(String message, boolean b) {
|
||||
super(message);
|
||||
eof = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusIOException</tt> instance with the given
|
||||
* message and underlying cause.
|
||||
* <p>
|
||||
*
|
||||
* @param message the message describing this <tt>ModbusIOException</tt>.
|
||||
* @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 <tt>ModbusIOException</tt> is caused by an end of the
|
||||
* stream.
|
||||
* <p>
|
||||
*
|
||||
* @return true if stream ended, false otherwise.
|
||||
*/
|
||||
public boolean isEOF() {
|
||||
return eof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag that determines whether this <tt>ModbusIOException</tt> was
|
||||
* caused by an end of the stream.
|
||||
* <p>
|
||||
*
|
||||
* @param b true if stream ended, false otherwise.
|
||||
*/
|
||||
public void setEOF(boolean b) {
|
||||
eof = b;
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusSlaveException</tt>. 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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructs a new <tt>ModbusSlaveException</tt> instance with the given
|
||||
* type.
|
||||
*
|
||||
* <p>
|
||||
* Types are defined according to the protocol specification in
|
||||
* <tt>net.wimpi.modbus.Modbus</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns the type of this <tt>ModbusSlaveException</tt>. <br>
|
||||
* Types are defined according to the protocol specification in
|
||||
* <tt>net.wimpi.modbus.Modbus</tt>.
|
||||
*
|
||||
* @return the type of this <tt>ModbusSlaveException</tt>.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Tests if this <tt>ModbusSlaveException</tt> is of a given type.
|
||||
*
|
||||
* <p>
|
||||
* Types are defined according to the protocol specification in
|
||||
* <tt>net.wimpi.modbus.Modbus</tt>.
|
||||
*
|
||||
* @param TYPE the type to test this <tt>ModbusSlaveException</tt> type
|
||||
* against.
|
||||
*
|
||||
* @return true if this <tt>ModbusSlaveException</tt> 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);
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusTCPMaster</tt> with the slave.
|
||||
*
|
||||
* @throws Exception if the connection cannot be established.
|
||||
*/
|
||||
abstract public void connect() throws Exception;
|
||||
|
||||
/**
|
||||
* Disconnects this <tt>ModbusTCPMaster</tt> 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 <tt>BitVector</tt> 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 <tt>BitVector</tt> 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 <tt>BitVector</tt> 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 <tt>InputRegister[]</tt> 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 <tt>Register[]</tt> 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 <tt>Register</tt> 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 <tt>Register[]</tt> 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 <tt>BitVector</tt> 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 <tt>BitVector</tt> 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 <tt>BitVector</tt> 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 <tt>InputRegister[]</tt> 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 <tt>Register[]</tt> 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 <tt>Register</tt> 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 <tt>Register[]</tt> 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 <tt>int</tt>.
|
||||
*/
|
||||
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();
|
||||
|
||||
}
|
@ -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 <tt>ModbusTCPMaster</tt> 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 <tt>ModbusTCPMaster</tt> 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();
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusTCPMaster</tt> 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 <tt>ModbusTCPMaster</tt> 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();
|
||||
}
|
||||
}
|
@ -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
|
||||
* <tt>ModbusMessage</tt> 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 <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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 <tt>ModbusMessage</tt> to the
|
||||
* output stream of this <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param msg a <tt>ModbusMessage</tt>.
|
||||
*
|
||||
* @throws ModbusIOException data cannot be
|
||||
* written properly to the raw output stream of
|
||||
* this <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public abstract void writeRequest(ModbusRequest msg) throws ModbusIOException;
|
||||
|
||||
/**
|
||||
* Writes a <tt>ModbusResponseMessage</tt> to the
|
||||
* output stream of this <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param msg a <tt>ModbusMessage</tt>.
|
||||
*
|
||||
* @throws ModbusIOException data cannot be
|
||||
* written properly to the raw output stream of
|
||||
* this <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public abstract void writeResponse(ModbusResponse msg) throws ModbusIOException;
|
||||
|
||||
/**
|
||||
* Reads a <tt>ModbusRequest</tt> from the
|
||||
* input stream of this <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param listener Listener the request was received by
|
||||
*
|
||||
* @return req the <tt>ModbusRequest</tt> read from the underlying stream.
|
||||
*
|
||||
* @throws ModbusIOException data cannot be
|
||||
* read properly from the raw input stream of
|
||||
* this <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public abstract ModbusRequest readRequest(AbstractModbusListener listener) throws ModbusIOException;
|
||||
|
||||
/**
|
||||
* Reads a <tt>ModbusResponse</tt> from the
|
||||
* input stream of this <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return res the <tt>ModbusResponse</tt> read from the underlying stream.
|
||||
*
|
||||
* @throws ModbusIOException data cannot be
|
||||
* read properly from the raw input stream of
|
||||
* this <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public abstract ModbusResponse readResponse() throws ModbusIOException;
|
||||
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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 <tt>BytesInputStream</tt> 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 <tt>BytesInputStream</tt> 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 <tt>BytesInputStream</tt> 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 <tt>BytesInputStream</tt> 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 <tt>BytesInputStream</tt> 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 <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>byte[]</tt> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>BytesOutputStream</tt> instance with
|
||||
* a new output buffer of the given size.
|
||||
*
|
||||
* @param size the size of the output buffer as <tt>int</tt>.
|
||||
*/
|
||||
public BytesOutputStream(int size) {
|
||||
super(size);
|
||||
dataOutputStream = new DataOutputStream(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>BytesOutputStream</tt> instance with
|
||||
* a given output buffer.
|
||||
*
|
||||
* @param buffer the output buffer as <tt>byte[]</tt>.
|
||||
*/
|
||||
public BytesOutputStream(byte[] buffer) {
|
||||
buf = buffer;
|
||||
count = 0;
|
||||
dataOutputStream = new DataOutputStream(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference to the output buffer.
|
||||
*
|
||||
* @return the reference to the <tt>byte[]</tt> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>byte[]</tt>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>byte[]</tt>.
|
||||
*/
|
||||
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
|
||||
* <tt>FastByteArrayOutputStream</tt>.
|
||||
*
|
||||
* @return the number of bytes written as <tt>int</tt>.
|
||||
*/
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this <tt>FastByteArrayOutputStream</tt>.
|
||||
*/
|
||||
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 <tt>FastByteArrayOutputStream</tt>
|
||||
* 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 <tt>FastByteArrayOutputStream</tt>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ModbusTransport</tt> instance, for a given
|
||||
* <tt>Socket</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param socket the <tt>Socket</tt> 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);
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusTransaction</tt> 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 <tt>ModbusTCPTransaction</tt> instance.
|
||||
*/
|
||||
public ModbusTCPTransaction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusTCPTransaction</tt> instance with a given
|
||||
* <tt>ModbusRequest</tt> to be send when the transaction is executed.
|
||||
* <p>
|
||||
*
|
||||
* @param request a <tt>ModbusRequest</tt> instance.
|
||||
*/
|
||||
public ModbusTCPTransaction(ModbusRequest request) {
|
||||
setRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusTCPTransaction</tt> instance with a given
|
||||
* <tt>TCPMasterConnection</tt> to be used for transactions.
|
||||
* <p>
|
||||
*
|
||||
* @param con a <tt>TCPMasterConnection</tt> instance.
|
||||
*/
|
||||
public ModbusTCPTransaction(TCPMasterConnection con) {
|
||||
setConnection(con);
|
||||
transport = con.getModbusTransport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the connection on which this <tt>ModbusTransaction</tt> should be
|
||||
* executed.
|
||||
* <p>
|
||||
* An implementation should be able to handle open and closed connections.
|
||||
* <br>
|
||||
* <p>
|
||||
*
|
||||
* @param con a <tt>TCPMasterConnection</tt>.
|
||||
*/
|
||||
public synchronized void setConnection(TCPMasterConnection con) {
|
||||
connection = con;
|
||||
transport = con.getModbusTransport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the connection will be opened and closed for <b>each</b>
|
||||
* execution.
|
||||
* <p>
|
||||
*
|
||||
* @return true if reconnecting, false otherwise.
|
||||
*/
|
||||
public boolean isReconnecting() {
|
||||
return reconnecting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag that controls whether a connection is opened and closed
|
||||
* for <b>each</b> execution or not.
|
||||
* <p>
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ModbusTransport</tt> instance, for a given
|
||||
* <tt>Socket</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param socket the <tt>Socket</tt> 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 <tt>Socket</tt> used for message transport and prepares the
|
||||
* streams used for the actual I/O.
|
||||
*
|
||||
* @param socket the <tt>Socket</tt> 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 <tt>ModbusTCPTransport</tt>
|
||||
* 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 <tt>ModbusMessage</tt> to the
|
||||
* output stream of this <tt>ModbusTransport</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param msg a <tt>ModbusMessage</tt>.
|
||||
* @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 <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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 <tt>ModbusRequest</tt> instance
|
||||
* associated with this <tt>ModbusTransaction</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the associated <tt>ModbusRequest</tt> instance.
|
||||
*/
|
||||
public ModbusRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <tt>ModbusRequest</tt> for this
|
||||
* <tt>ModbusTransaction</tt>.<p>
|
||||
* The related <tt>ModbusResponse</tt> is acquired
|
||||
* from the passed in <tt>ModbusRequest</tt> instance.<br>
|
||||
* <p>
|
||||
*
|
||||
* @param req a <tt>ModbusRequest</tt>.
|
||||
*/
|
||||
public void setRequest(ModbusRequest req) {
|
||||
request = req;
|
||||
if (req != null) {
|
||||
request.setTransactionID(getTransactionID());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>ModbusResponse</tt> instance
|
||||
* associated with this <tt>ModbusTransaction</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the associated <tt>ModbusRequest</tt> instance.
|
||||
*/
|
||||
public ModbusResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of retries for opening
|
||||
* the connection for executing the transaction.
|
||||
* <p>
|
||||
*
|
||||
* @return the amount of retries as <tt>int</tt>.
|
||||
*/
|
||||
int getRetries() {
|
||||
return retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of retries for opening
|
||||
* the connection for executing the transaction.
|
||||
* <p>
|
||||
*
|
||||
* @param retries the amount of retries as <tt>int</tt>.
|
||||
*/
|
||||
public void setRetries(int retries) {
|
||||
this.retries = retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the validity of a transaction
|
||||
* will be checked.
|
||||
* <p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @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 <tt>ModbusTransaction</tt>.
|
||||
* Locks the <tt>ModbusTransport</tt> for sending
|
||||
* the <tt>ModbusRequest</tt> and reading the
|
||||
* related <tt>ModbusResponse</tt>.
|
||||
* If reconnecting is activated the connection will
|
||||
* be opened for the transaction and closed afterwards.
|
||||
* <p>
|
||||
*
|
||||
* @throws ModbusException if an I/O error occurs,
|
||||
* or the response is a modbus protocol exception.
|
||||
*/
|
||||
public abstract void execute() throws ModbusException;
|
||||
|
||||
}
|
@ -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 <tt>ModbusTransaction</tt>
|
||||
* 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 <tt>ModbusUDPTransaction</tt>
|
||||
* instance.
|
||||
*/
|
||||
public ModbusUDPTransaction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusUDPTransaction</tt>
|
||||
* instance with a given <tt>ModbusRequest</tt> to
|
||||
* be send when the transaction is executed.
|
||||
* <p>
|
||||
*
|
||||
* @param request a <tt>ModbusRequest</tt> instance.
|
||||
*/
|
||||
public ModbusUDPTransaction(ModbusRequest request) {
|
||||
setRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusUDPTransaction</tt>
|
||||
* instance with a given <tt>UDPTerminal</tt> to
|
||||
* be used for transactions.
|
||||
* <p>
|
||||
*
|
||||
* @param terminal a <tt>UDPTerminal</tt> instance.
|
||||
*/
|
||||
public ModbusUDPTransaction(AbstractUDPTerminal terminal) {
|
||||
setTerminal(terminal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ModbusUDPTransaction</tt>
|
||||
* instance with a given <tt>ModbusUDPConnection</tt>
|
||||
* to be used for transactions.
|
||||
* <p>
|
||||
*
|
||||
* @param con a <tt>ModbusUDPConnection</tt> instance.
|
||||
*/
|
||||
public ModbusUDPTransaction(UDPMasterConnection con) {
|
||||
setTerminal(con.getTerminal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the terminal on which this <tt>ModbusTransaction</tt>
|
||||
* should be executed.<p>
|
||||
*
|
||||
* @param terminal a <tt>UDPSlaveTerminal</tt>.
|
||||
*/
|
||||
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 <tt>ModbusTCPTransaction</tt> 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.<br>
|
||||
* 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());
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusTransport</tt> instance,
|
||||
* for a given <tt>UDPTerminal</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param terminal the <tt>UDPTerminal</tt> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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 <tt>byte[]</tt>.
|
||||
*/
|
||||
byte[] getData();
|
||||
|
||||
/**
|
||||
* Reads the non-word raw data based on an arbitrary implemented structure.
|
||||
*
|
||||
* @param in the <tt>DataInput</tt> to read from.
|
||||
* @param reference to specify the offset as <tt>int</tt>.
|
||||
* @param count to specify the amount of bytes as <tt>int</tt>.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* This method is called for a message (for example, a
|
||||
* <tt>WriteMultipleRegistersRequest</tt> 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.
|
||||
*
|
||||
* <p>
|
||||
* This method is called for a message (for example, * <tt>ReadMultipleRegistersRequest</tt>) when finished with reading, for
|
||||
* creating a response.
|
||||
*
|
||||
* @param reference to specify the offset as <tt>int</tt>.
|
||||
* @param count to specify the number of bytes as <tt>int</tt>.
|
||||
*/
|
||||
void prepareData(int reference, int count);
|
||||
}
|
@ -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 a<tt>ModbusResponse</tt> 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 <tt>ExceptionResponse</tt> 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 <tt>int</tt>.
|
||||
* @param exc the exception code as <tt>int</tt>.
|
||||
*/
|
||||
public ExceptionResponse(int fc, int exc) {
|
||||
|
||||
// One byte of data.
|
||||
setDataLength(1);
|
||||
setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
|
||||
|
||||
exceptionCode = exc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ExceptionResponse</tt> instance with a given
|
||||
* function code. ORs the exception offset automatically.
|
||||
*
|
||||
* @param fc the function code as <tt>int</tt>.
|
||||
*/
|
||||
public ExceptionResponse(int fc) {
|
||||
|
||||
// One byte of data.
|
||||
setDataLength(1);
|
||||
setFunctionCode(fc | Modbus.EXCEPTION_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ExceptionResponse</tt> instance with no function
|
||||
* or exception code.
|
||||
*/
|
||||
public ExceptionResponse() {
|
||||
|
||||
// One byte of data.
|
||||
setDataLength(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Modbus exception code of this <tt>ExceptionResponse</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the exception code as <tt>int</tt>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Class implementing a <tt>ModbusRequest</tt> which is created for illegal or
|
||||
* non implemented function codes.
|
||||
*
|
||||
* <p>
|
||||
* 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 <tt>IllegalFunctionRequest</tt> instance for a given
|
||||
* function code.
|
||||
*
|
||||
* <p>Used to implement slave devices when an illegal function code
|
||||
* has been requested.
|
||||
*
|
||||
* @param function the function code as <tt>int</tt>.
|
||||
*/
|
||||
public IllegalFunctionRequest(int function) {
|
||||
setFunctionCode(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>IllegalFunctionRequest</tt> instance for a given
|
||||
* function code.
|
||||
*
|
||||
* <p>Used to implement slave devices when an illegal function code
|
||||
* has been requested.
|
||||
*
|
||||
* @param unit Unit ID
|
||||
* @param function the function code as <tt>int</tt>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 <tt>Mask Write Register</tt> 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 <tt>Mask Write Register</tt> 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 <tt>Mask Write Register</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadMEIResponse</tt>.
|
||||
*
|
||||
* 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 <tt>ReportSlaveIDResponse</tt>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusMessage</tt> 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 <tt>ModbusMessage</tt> as headless (for
|
||||
* serial transport).
|
||||
*/
|
||||
void setHeadless();
|
||||
|
||||
/**
|
||||
* Returns the transaction identifier of this <tt>ModbusMessage</tt> as
|
||||
* <tt>int</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The identifier is a 2-byte (short) non negative integer value valid in
|
||||
* the range of 0-65535.
|
||||
*
|
||||
* @return the transaction identifier as <tt>int</tt>.
|
||||
*/
|
||||
int getTransactionID();
|
||||
|
||||
/**
|
||||
* Returns the protocol identifier of this <tt>ModbusMessage</tt> as
|
||||
* <tt>int</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The identifier is a 2-byte (short) non negative integer value valid in
|
||||
* the range of 0-65535.
|
||||
*
|
||||
* @return the protocol identifier as <tt>int</tt>.
|
||||
*/
|
||||
int getProtocolID();
|
||||
|
||||
/**
|
||||
* Returns the length of the data appended after the protocol header.
|
||||
* <p>
|
||||
*
|
||||
* @return the data length as <tt>int</tt>.
|
||||
*/
|
||||
int getDataLength();
|
||||
|
||||
/**
|
||||
* Returns the unit identifier of this <tt>ModbusMessage</tt> as
|
||||
* <tt>int</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The identifier is a 1-byte non negative integer value valid in the range
|
||||
* of 0-255.
|
||||
*
|
||||
* @return the unit identifier as <tt>int</tt>.
|
||||
*/
|
||||
int getUnitID();
|
||||
|
||||
/**
|
||||
* Returns the function code of this <tt>ModbusMessage</tt> as <tt>int</tt>.<br>
|
||||
* The function code is a 1-byte non negative integer value valid in the
|
||||
* range of 0-127.
|
||||
*
|
||||
* <p>
|
||||
* Function codes are ordered in conformance classes their values are
|
||||
* specified in <tt>com.ghgande.j2mod.modbus.Modbus</tt>.
|
||||
*
|
||||
* @return the function code as <tt>int</tt>.
|
||||
*
|
||||
* @see com.ghgande.j2mod.modbus.Modbus
|
||||
*/
|
||||
int getFunctionCode();
|
||||
|
||||
/**
|
||||
* Returns the <i>raw</i> message as an array of bytes.
|
||||
* <p>
|
||||
*
|
||||
* @return the <i>raw</i> message as <tt>byte[]</tt>.
|
||||
*/
|
||||
byte[] getMessage();
|
||||
|
||||
/**
|
||||
* Returns the <i>raw</i> message as <tt>String</tt> containing a
|
||||
* hexadecimal series of bytes.
|
||||
*
|
||||
* <p>
|
||||
* This method is specially for debugging purposes, allowing the user to log
|
||||
* the communication in a manner used in the specification document.
|
||||
*
|
||||
* @return the <i>raw</i> message as <tt>String</tt> 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 <tt>int</tt>.
|
||||
*/
|
||||
int getOutputLength();
|
||||
|
||||
/**
|
||||
* Writes this <tt>Transportable</tt> to the
|
||||
* given <tt>DataOutput</tt>.
|
||||
*
|
||||
* @param dout the <tt>DataOutput</tt> to write to.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
void writeTo(DataOutput dout) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads this <tt>Transportable</tt> from the given
|
||||
* <tt>DataInput</tt>.
|
||||
*
|
||||
* @param din the <tt>DataInput</tt> to read from.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs or the data
|
||||
* is invalid.
|
||||
*/
|
||||
void readFrom(DataInput din) throws IOException;
|
||||
|
||||
|
||||
}
|
@ -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 <tt>ModbusMessage</tt>. 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 <tt>ModbusMessage</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The identifier must be a 2-byte (short) non negative integer value valid
|
||||
* in the range of 0-65535.<br>
|
||||
*
|
||||
* @param tid the transaction identifier as <tt>int</tt>.
|
||||
*/
|
||||
public void setTransactionID(int tid) {
|
||||
transactionID = tid & 0x0000FFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolID() {
|
||||
return protocolID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the protocol identifier of this <tt>ModbusMessage</tt>.
|
||||
* <p>
|
||||
* The identifier should be a 2-byte (short) non negative integer value
|
||||
* valid in the range of 0-65535.<br>
|
||||
* <p>
|
||||
*
|
||||
* @param pid the protocol identifier as <tt>int</tt>.
|
||||
*/
|
||||
public void setProtocolID(int pid) {
|
||||
protocolID = pid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataLength() {
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the length of the data appended after the protocol header.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This method does not include the length of a final CRC/LRC for those
|
||||
* protocols which requirement.
|
||||
*
|
||||
* @param length the data length as <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>ModbusMessage</tt>.<br>
|
||||
* 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 <tt>ModbusMessage</tt>.<br>
|
||||
* The function code should be a 1-byte non negative integer value valid in
|
||||
* the range of 0-127.<br>
|
||||
* Function codes are ordered in conformance classes their values are
|
||||
* specified in <tt>com.ghgande.j2mod.modbus.Modbus</tt>.
|
||||
*
|
||||
* @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;
|
||||
|
||||
}
|
@ -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 <tt>ModbusRequest</tt>. 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 <tt>ModbusRequest</tt>
|
||||
* instance.
|
||||
*
|
||||
* @param functionCode the function code of the request as <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>ModbusResponse</tt> that correlates with this
|
||||
* <tt>ModbusRequest</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The response must include the unit number, function code, as well as any
|
||||
* transport-specific header information.
|
||||
*
|
||||
* <p>
|
||||
* 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 <tt>ModbusResponse</tt>.
|
||||
*/
|
||||
public abstract ModbusResponse getResponse();
|
||||
|
||||
/**
|
||||
* Returns the <tt>ModbusResponse</tt> that represents the answer to this
|
||||
* <tt>ModbusRequest</tt>.
|
||||
*
|
||||
* <p>
|
||||
* The implementation should take care about assembling the reply to this
|
||||
* <tt>ModbusRequest</tt>.
|
||||
*
|
||||
* <p>
|
||||
* 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 <tt>ModbusResponse</tt>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <tt>ModbusResponse</tt>. 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 <tt>ModbusResponse</tt>
|
||||
* instance.
|
||||
*
|
||||
* @param functionCode the function code of the response as <tt>int</tt>.
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @param msg the <tt>byte[]</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadCoilsRequest</tt>. The implementation directly
|
||||
* correlates with the class 1 function <i>read coils (FC 1)</i>. It
|
||||
* encapsulates the corresponding request message.
|
||||
*
|
||||
* <p>
|
||||
* 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 <tt>ReadCoilsRequest</tt> instance.
|
||||
*/
|
||||
public ReadCoilsRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_COILS);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadCoilsRequest</tt> instance with a given
|
||||
* reference and count of coils (i.e. bits) to be read.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start reading from as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start reading from with this
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadCoilsResponse</tt>.
|
||||
* The implementation directly correlates with the class 1
|
||||
* function <i>read coils (FC 1)</i>. It encapsulates
|
||||
* the corresponding response message.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read MEI Data</tt> 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 <tt>Report Slave ID request</tt> 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];
|
||||
}
|
||||
}
|
@ -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 <tt>ReadCommEventCounterResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReportSlaveIDResponse</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read MEI Data</tt> 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 <tt>Get Comm Event Log</tt>
|
||||
* 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];
|
||||
}
|
||||
}
|
@ -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 <tt>ReadCommEventCounterResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReadCommEventLogResponse</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read Exception Status</tt> 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 <tt>Read Exception Status</tt> 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];
|
||||
}
|
||||
}
|
@ -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 <tt>ReadCommEventCounterResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReadExceptionStatusResponse</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read FIFO Queue</tt> 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 <tt>Read FIFO Queue</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadFIFOQueueResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReadFIFOQueueResponse</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read File Record</tt> 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 <tt>Read File Record</tt> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>ReadFileRecordResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReadFileRecordResponse</tt> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>ReadInputDiscretesRequest</tt>. The implementation
|
||||
* directly correlates with the class 1 function <i>read input discretes (FC
|
||||
* 2)</i>. It encapsulates the corresponding request message.
|
||||
* <p>
|
||||
* 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 <tt>ReadInputDiscretesRequest</tt> instance.
|
||||
*/
|
||||
public ReadInputDiscretesRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_INPUT_DISCRETES);
|
||||
|
||||
// Two bytes for count, two bytes for offset.
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadInputDiscretesRequest</tt> instance with a given
|
||||
* reference and count of input discretes (i.e. bits) to be read.
|
||||
* <p>
|
||||
*
|
||||
* @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 <tt>ReadInputDiscretesRequest</tt>.
|
||||
*
|
||||
* @return the reference of the discrete to start reading from as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start reading from with this
|
||||
* <tt>ReadInputDiscretesRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadInputDiscretesRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadInputDiscretesRequest</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadInputDiscretesResponse</tt>.
|
||||
* The implementation directly correlates with the class 1
|
||||
* function <i>read input discretes (FC 2)</i>. It encapsulates
|
||||
* the corresponding response message.
|
||||
* <p>
|
||||
* 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 <tt>ReadInputDiscretesResponse</tt>
|
||||
* instance.
|
||||
*/
|
||||
public ReadInputDiscretesResponse() {
|
||||
super();
|
||||
setFunctionCode(Modbus.READ_INPUT_DISCRETES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadInputDiscretesResponse</tt>
|
||||
* 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 <tt>BitVector</tt> that stores
|
||||
* the collection of bits that have been read.
|
||||
* <p>
|
||||
*
|
||||
* @return the <tt>BitVector</tt> 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.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ReadInputRegistersRequest</tt>. The implementation
|
||||
* directly correlates with the class 0 function <i>read multiple registers (FC
|
||||
* 4)</i>. 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 <tt>ReadInputRegistersRequest</tt> instance.
|
||||
*/
|
||||
public ReadInputRegistersRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_INPUT_REGISTERS);
|
||||
// 4 bytes (unit id and function code is excluded)
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadInputRegistersRequest</tt> instance with a given
|
||||
* reference and count of words to be read.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadInputRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start reading from as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start reading from with this
|
||||
* <tt>ReadInputRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadInputRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the number of words to be read as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
return wordCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of words to be read with this
|
||||
* <tt>ReadInputRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadInputRegistersRequest</tt>. The implementation
|
||||
* directly correlates with the class 0 function <i>read multiple registers (FC
|
||||
* 4)</i>. 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 <tt>ReadInputRegistersResponse</tt> instance.
|
||||
*/
|
||||
public ReadInputRegistersResponse() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_INPUT_REGISTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadInputRegistersResponse</tt> 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 <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>InputRegister</tt> at the given position (relative to the
|
||||
* reference used in the request).
|
||||
*
|
||||
* @param index the relative index of the <tt>InputRegister</tt>.
|
||||
*
|
||||
* @return the register as <tt>InputRegister</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>InputRegister[]</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read MEI Data</tt> 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 <tt>Read MEI Data request</tt> instance.
|
||||
*/
|
||||
public ReadMEIRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_MEI);
|
||||
subCode = 0x0E;
|
||||
|
||||
// 3 bytes (unit id and function code is excluded)
|
||||
setDataLength(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>Read MEI Data request</tt> instance with a given
|
||||
* reference and count of coils (i.e. bits) to be read.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start reading from as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public int getLevel() {
|
||||
return fieldLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start reading from with this
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadMEIResponse</tt>.
|
||||
*
|
||||
* 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 <tt>ReadMEIResponse</tt>
|
||||
* instance.
|
||||
*/
|
||||
public ReadMEIResponse() {
|
||||
super();
|
||||
setFunctionCode(Modbus.READ_MEI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of fields
|
||||
* read with the request.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ReadMultipleRegistersRequest</tt>. The
|
||||
* implementation directly correlates with the class 0 function <i>read multiple
|
||||
* registers (FC 3)</i>. 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 <tt>ReadMultipleRegistersRequest</tt> instance.
|
||||
*/
|
||||
public ReadMultipleRegistersRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadMultipleRegistersRequest</tt> 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
|
||||
* <tt>ReadMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start reading from as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start reading from with this
|
||||
* <tt>ReadMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>ReadMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the number of words to be read as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
return wordCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of words to be read with this
|
||||
* <tt>ReadMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadMultipleRegistersResponse</tt>. The
|
||||
* implementation directly correlates with the class 0 function <i>read multiple
|
||||
* registers (FC 3)</i>. 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 <tt>ReadMultipleRegistersResponse</tt> instance.
|
||||
*/
|
||||
public ReadMultipleRegistersResponse() {
|
||||
super();
|
||||
setFunctionCode(Modbus.READ_MULTIPLE_REGISTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>ReadInputRegistersResponse</tt> 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 <tt>int</tt>.
|
||||
*/
|
||||
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
|
||||
* <tt>ReadMultipleRegistersResponse</tt>.
|
||||
*
|
||||
* @return the number of words that have been read as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
return byteCount / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>Register</tt> at the given position (relative to the
|
||||
* reference used in the request).
|
||||
*
|
||||
* @param index the relative index of the <tt>Register</tt>.
|
||||
*
|
||||
* @return the register as <tt>Register</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>Register[]</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadSerialDiagnosticsRequest</tt>.
|
||||
*
|
||||
* @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 <tt>Diagnostics</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadSerialDiagnosticsResponse</tt>.
|
||||
*
|
||||
* @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 <tt>Diagnostics</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read / Write Multiple Registers</tt> 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 <tt>Read/Write Multiple Registers Request</tt> 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 <tt>Read/Write Multiple Registers Request</tt> 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 <tt>Read/Write Multiple Registers Request</tt> 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 <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start writing to as <tt>int</tt>
|
||||
* .
|
||||
*/
|
||||
public int getReadReference() {
|
||||
return readReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* setReadReference - Sets the reference of the register to writing to with
|
||||
* this <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param ref the reference of the register to start writing to as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public void setReadReference(int ref) {
|
||||
readReference = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* getWriteReference - Returns the reference of the register to start
|
||||
* writing to with this <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start writing to as <tt>int</tt>
|
||||
* .
|
||||
*/
|
||||
public int getWriteReference() {
|
||||
return writeReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* setWriteReference - Sets the reference of the register to write to with
|
||||
* this <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param ref the reference of the register to start writing to as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public void setWriteReference(int ref) {
|
||||
writeReference = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegisters - Returns the registers to be written with this
|
||||
* <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the registers to be read as <tt>Register[]</tt>.
|
||||
*/
|
||||
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
|
||||
* <tt>ReadWriteMultipleRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param registers the registers to be written as <tt>Register[]</tt>.
|
||||
*/
|
||||
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 <tt>Register</tt>.
|
||||
*
|
||||
* @param index the index of the <tt>Register</tt>.
|
||||
*
|
||||
* @return the register as <tt>Register</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>.
|
||||
*/
|
||||
public int getByteCount() {
|
||||
return getWriteWordCount() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* getWriteWordCount - Returns the number of words to be written.
|
||||
*
|
||||
* @return the number of words to be written as <tt>int</tt>.
|
||||
*/
|
||||
public int getWriteWordCount() {
|
||||
return writeCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* setWriteWordCount - Sets the number of words to be written.
|
||||
*
|
||||
* @param count the number of words to be written as <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>int</tt>.
|
||||
*/
|
||||
public int getReadWordCount() {
|
||||
return readCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* setReadWordCount - Sets the number of words to be read.
|
||||
*
|
||||
* @param count the number of words to be read as <tt>int</tt>.
|
||||
*/
|
||||
public void setReadWordCount(int count) {
|
||||
readCount = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* getNonWordDataHandler - Returns the actual non word data handler.
|
||||
*
|
||||
* @return the actual <tt>NonWordDataHandler</tt>.
|
||||
*/
|
||||
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 <tt>NonWordDataHandler</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>ReadWriteMultipleResponse</tt>.
|
||||
*
|
||||
* @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 <tt>ReadWriteMultipleResponse</tt> 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 <tt>ReadWriteMultipleResponse</tt> 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 <tt>ReadWriteMultipleResponse</tt> 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 <tt>int</tt>.
|
||||
*/
|
||||
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
|
||||
* <tt>ReadWriteMultipleResponse</tt>.
|
||||
*
|
||||
* @return the number of words that have been read as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
return byteCount / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>Register</tt> at the given position (relative to the
|
||||
* reference used in the request).
|
||||
*
|
||||
* @param index the relative index of the <tt>InputRegister</tt>.
|
||||
*
|
||||
* @return the register as <tt>InputRegister</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>.
|
||||
*
|
||||
* @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 <tt>InputRegister[]</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>Read MEI Data</tt> 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 <tt>Report Slave ID request</tt>
|
||||
* 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];
|
||||
}
|
||||
}
|
@ -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 <tt>ReadMEIResponse</tt>.
|
||||
*
|
||||
* 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 <tt>ReportSlaveIDResponse</tt>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteCoilRequest</tt>. The implementation directly
|
||||
* correlates with the class 0 function <i>write coil (FC 5)</i>. 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 <tt>WriteCoilRequest</tt> instance.
|
||||
*/
|
||||
public WriteCoilRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.WRITE_COIL);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteCoilRequest</tt> 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 <tt>ReadCoilsRequest</tt>.
|
||||
*
|
||||
* @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 <tt>ReadCoilsRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @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
|
||||
* <tt>WriteCoilRequest</tt>.
|
||||
*
|
||||
* @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 <tt>WriteCoilRequest</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteCoilResponse</tt>. The implementation directly
|
||||
* correlates with the class 0 function <i>write coil (FC 5)</i>. 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 <tt>WriteCoilResponse</tt> instance.
|
||||
*/
|
||||
public WriteCoilResponse() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.WRITE_COIL);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteCoilResponse</tt> 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 <tt>WriteCoilRequest</tt>.
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>Write File Record</tt> 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 <tt>Write File Record</tt> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>WriteFileRecordResponse</tt>.
|
||||
*
|
||||
* @author Julie
|
||||
* @author Steve O'Hara (4NG)
|
||||
* @version 2.0 (March 2016)
|
||||
*/
|
||||
public final class WriteFileRecordResponse extends ModbusResponse {
|
||||
private RecordResponse[] records;
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteFileRecordResponse</tt> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>WriteMultipleCoilsRequest</tt>. The implementation
|
||||
* directly correlates with the class 1 function <i>write multiple coils (FC
|
||||
* 15)</i>. It encapsulates the corresponding request message.
|
||||
*
|
||||
* <p>
|
||||
* Coils are understood as bits that can be manipulated (i.e. set or cleared).
|
||||
* </p>
|
||||
*
|
||||
* @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 <tt>WriteMultipleCoilsRequest</tt> 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 <tt>WriteMultipleCoilsRequest</tt> instance with a given
|
||||
* reference and count of coils to be written, followed by the actual byte
|
||||
* count, and then <i>count</i> 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 <tt>WriteMultipleCoilsRequest</tt> instance.
|
||||
*
|
||||
* <p>
|
||||
* A minimal message contains the reference to the first coil as a
|
||||
* <tt>short</tt>, the number of coils as a <tt>short</tt>, and not less
|
||||
* than one <tt>byte</tt> 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 <tt>WriteMultipleCoilsRequest</tt>.
|
||||
*
|
||||
* @return the reference of the coil to start writing to as an <tt>int</tt>.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* setReference - Sets the reference of the coil to start writing to with
|
||||
* this <tt>WriteMultipleCoilsRequest</tt>.
|
||||
*
|
||||
* @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 <tt>BitVector</tt> instance holding coil status
|
||||
* information.
|
||||
*
|
||||
* @return the coils status as a <tt>BitVector</tt> instance.
|
||||
*/
|
||||
public BitVector getCoils() {
|
||||
return coils;
|
||||
}
|
||||
|
||||
/**
|
||||
* setCoils - Sets the <tt>BitVector</tt> instance holding coil status
|
||||
* information.
|
||||
*
|
||||
* @param bv a <tt>BitVector</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteMultipleCoilsResponse</tt>. The implementation
|
||||
* directly correlates with the class 1 function <i>write multiple coils (FC
|
||||
* 15)</i>. It encapsulates the corresponding response message.
|
||||
* <p>
|
||||
* 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 <tt>WriteMultipleCoilsResponse</tt> instance with a
|
||||
* given count of coils and starting reference.
|
||||
* <p>
|
||||
*
|
||||
* @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 <tt>WriteMultipleCoilsResponse</tt> instance.
|
||||
*/
|
||||
public WriteMultipleCoilsResponse() {
|
||||
super();
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* getReference - Returns the reference of the coil to start reading from
|
||||
* with this <tt>WriteMultipleCoilsResponse</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the coil to start reading from as <tt>int</tt>.
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteMultipleRegistersRequest</tt>. The
|
||||
* implementation directly correlates with the class 0 function <i>write
|
||||
* multiple registers (FC 16)</i>. 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 <tt>WriteMultipleRegistersRequest</tt> instance with a
|
||||
* given starting reference and values to be written.
|
||||
* <p>
|
||||
*
|
||||
* @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 <tt>WriteMultipleRegistersRequest</tt> instance.
|
||||
*/
|
||||
public WriteMultipleRegistersRequest() {
|
||||
setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModbusResponse getResponse() {
|
||||
return updateResponseWithHeader(new WriteMultipleRegistersResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* createResponse - Returns the <tt>WriteMultipleRegistersResponse</tt> that
|
||||
* represents the answer to this <tt>WriteMultipleRegistersRequest</tt>.
|
||||
*
|
||||
* The implementation should take care about assembling the reply to this
|
||||
* <tt>WriteMultipleRegistersRequest</tt>.
|
||||
*
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* 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
|
||||
* <tt>short</tt> 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 <tt>WriteMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start writing to as <tt>int</tt>
|
||||
* .
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* setReference - Sets the reference of the register to write to with this
|
||||
* <tt>WriteMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param ref the reference of the register to start writing to as an
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
public void setReference(int ref) {
|
||||
reference = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegisters - Returns the registers to be written with this
|
||||
* <tt>WriteMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the registers to be written as <tt>Register[]</tt>.
|
||||
*/
|
||||
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
|
||||
* <tt>WriteMultipleRegistersRequest</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param registers the registers to be written as <tt>Register[]</tt>.
|
||||
*/
|
||||
public void setRegisters(Register[] registers) {
|
||||
this.registers = registers == null ? null : Arrays.copyOf(registers, registers.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* getRegister - Returns the <tt>Register</tt> at the given position.
|
||||
*
|
||||
* @param index the relative index of the <tt>Register</tt>.
|
||||
*
|
||||
* @return the register as <tt>Register</tt>.
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @param index the index of the desired register.
|
||||
*
|
||||
* @return the value as an <tt>int</tt>.
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
*
|
||||
* @return the number of bytes to be written as <tt>int</tt>.
|
||||
*/
|
||||
public int getByteCount() {
|
||||
return getWordCount() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* getWordCount - Returns the number of words to be written.
|
||||
*
|
||||
* @return the number of words to be written as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
if (registers == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return registers.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* getNonWordDataHandler - Returns the actual non word data handler.
|
||||
*
|
||||
* @return the actual <tt>NonWordDataHandler</tt>.
|
||||
*/
|
||||
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 <tt>NonWordDataHandler</tt> 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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteMultipleRegistersResponse</tt>. The
|
||||
* implementation directly correlates with the class 0 function <i>read multiple
|
||||
* registers (FC 16)</i>. 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 <tt>WriteMultipleRegistersResponse</tt> instance.
|
||||
*/
|
||||
public WriteMultipleRegistersResponse() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.WRITE_MULTIPLE_REGISTERS);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteMultipleRegistersResponse</tt> 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
|
||||
* <tt>WriteMultipleRegistersResponse</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the register to start writing to as <tt>int</tt>
|
||||
* .
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register to start writing to with this
|
||||
* <tt>WriteMultipleRegistersResponse</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @param ref the reference of the register to start writing to as
|
||||
* <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>int</tt>.
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
*
|
||||
* @return the number of words that have been read as <tt>int</tt>.
|
||||
*/
|
||||
public int getWordCount() {
|
||||
return wordCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of words that have been returned.
|
||||
*
|
||||
* @param count the number of words as <tt>int</tt>.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteSingleRegisterRequest</tt>. The implementation
|
||||
* directly correlates with the class 0 function <i>write single register (FC
|
||||
* 6)</i>. 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 <tt>WriteSingleRegisterRequest</tt> instance.
|
||||
*/
|
||||
public WriteSingleRegisterRequest() {
|
||||
super();
|
||||
|
||||
setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
|
||||
setDataLength(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteSingleRegisterRequest</tt> 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
|
||||
* <tt>WriteSingleRegisterRequest</tt>.
|
||||
*
|
||||
* @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
|
||||
* <tt>WriteSingleRegisterRequest</tt>.
|
||||
*
|
||||
* @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
|
||||
* <tt>WriteSingleRegisterRequest</tt>.
|
||||
*
|
||||
* @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
|
||||
* <tt>WriteSingleRegisterRequest</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 <tt>WriteSingleRegisterResponse</tt>.
|
||||
* The implementation directly correlates with the class 0
|
||||
* function <i>write single register (FC 6)</i>. 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 <tt>WriteSingleRegisterResponse</tt>
|
||||
* instance.
|
||||
*/
|
||||
public WriteSingleRegisterResponse() {
|
||||
super();
|
||||
setDataLength(4);
|
||||
setFunctionCode(Modbus.WRITE_SINGLE_REGISTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <tt>WriteSingleRegisterResponse</tt>
|
||||
* 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 <tt>WriteSingleRegisterResponse</tt>.
|
||||
* <p>
|
||||
*
|
||||
* @return the value of the register.
|
||||
*/
|
||||
public int getRegisterValue() {
|
||||
return registerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value that has been returned in the
|
||||
* response message.
|
||||
* <p>
|
||||
*
|
||||
* @param value the returned register value.
|
||||
*/
|
||||
private void setRegisterValue(int value) {
|
||||
registerValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reference of the register
|
||||
* that has been written to.
|
||||
* <p>
|
||||
*
|
||||
* @return the reference of the written register.
|
||||
*/
|
||||
public int getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference of the register that has
|
||||
* been written to.
|
||||
* <p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ModbusListener</tt> instance.
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* Sets the port to be listened to.
|
||||
*
|
||||
* @param port the number of the IP port as <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>InetAddress</tt> 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 <tt>ModbusTCPListener</tt> 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 <tt>ModbusTCPListener</tt> object.
|
||||
* A <tt>ModbusTCPListener</tt> will silently drop any requests if the
|
||||
* listening state is set to <tt>false</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>ModbusTransport</tt> 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 <tt>ModbusTransport</tt> instance to be used for receiving
|
||||
* and sending messages.
|
||||
*
|
||||
* @return a <tt>ModbusTransport</tt> 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 <tt>String</tt> 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 <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @return the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public abstract int getTimeout();
|
||||
|
||||
/**
|
||||
* Sets the timeout for this <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @param timeout the timeout as <tt>int</tt>.
|
||||
*/
|
||||
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<String> getCommPorts();
|
||||
|
||||
}
|
@ -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 <tt>UDPTerminal</tt>.
|
||||
*
|
||||
* @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 <tt>UDPSlaveTerminal</tt> is active.
|
||||
*
|
||||
* @return <tt>true</tt> if active, <tt>false</tt> otherwise.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout in milliseconds for this <tt>UDPSlaveTerminal</tt>.
|
||||
*
|
||||
* @param timeout the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public synchronized void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transport
|
||||
* @return Transport
|
||||
*/
|
||||
public ModbusUDPTransport getTransport() {
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate this <tt>UDPTerminal</tt>.
|
||||
*
|
||||
* @throws Exception if there is a network failure.
|
||||
*/
|
||||
public abstract void activate() throws Exception;
|
||||
|
||||
/**
|
||||
* Deactivates this <tt>UDPTerminal</tt>.
|
||||
*/
|
||||
public abstract void deactivate();
|
||||
|
||||
/**
|
||||
* Sends the given message.
|
||||
*
|
||||
* @param msg the message as <tt>byte[]</tt>.
|
||||
*
|
||||
* @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 <tt>byte[]</tt>.
|
||||
*
|
||||
* @throws Exception if receiving a message fails.
|
||||
*/
|
||||
public abstract byte[] receiveMessage() throws Exception;
|
||||
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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.<br>
|
||||
*
|
||||
* @param poolsize the size of the <tt>ThreadPool</tt> 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.<br>
|
||||
*
|
||||
* @param poolsize the size of the <tt>ThreadPool</tt> 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 <tt>ThreadPool</tt> 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 <tt>ThreadPool</tt> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.<br>
|
||||
*
|
||||
* @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 <tt>ModbusUDPListener</tt> instance listening to the given
|
||||
* interface address.
|
||||
*
|
||||
* @param ifc an <tt>InetAddress</tt> 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 <tt>ModbusUDPListener</tt>.
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
@ -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 <tt>TCPConnectionHandler</tt> instance.
|
||||
*
|
||||
* <p>
|
||||
* The connections will be handling using the <tt>ModbusCouple</tt> class
|
||||
* and a <tt>ProcessImage</tt> which provides the interface between the
|
||||
* slave implementation and the <tt>TCPSlaveConnection</tt>.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>TCPMasterConnection</tt> instance with a given
|
||||
* destination address.
|
||||
*
|
||||
* @param adr the destination <tt>InetAddress</tt>.
|
||||
*/
|
||||
public TCPMasterConnection(InetAddress adr) {
|
||||
address = adr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the associated <tt>ModbusTransport</tt> of this
|
||||
* <tt>TCPMasterConnection</tt> 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 <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @throws Exception if there is a network failure.
|
||||
*/
|
||||
public synchronized void connect() throws Exception {
|
||||
connect(useRtuOverTcp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @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 <tt>TCPMasterConnection</tt> is connected.
|
||||
*
|
||||
* @return <tt>true</tt> if connected, <tt>false</tt> 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 <tt>TCPMasterConnection</tt>.
|
||||
*/
|
||||
public void close() {
|
||||
if (connected) {
|
||||
try {
|
||||
transport.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.debug("close()", ex);
|
||||
}
|
||||
finally {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>ModbusTransport</tt> associated with this
|
||||
* <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @return the connection's <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public AbstractModbusTransport getModbusTransport() {
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the <tt>ModbusTransport</tt> associated with this
|
||||
* <tt>TCPMasterConnection</tt>
|
||||
* @param trans associated transport
|
||||
*/
|
||||
public void setModbusTransport(ModbusTCPTransport trans) {
|
||||
transport = trans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timeout (msec) for this <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @return the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public synchronized int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout (msec) for this <tt>TCPMasterConnection</tt>. This is both the
|
||||
* connection timeout and the transaction timeout
|
||||
*
|
||||
* @param timeout - the timeout in milliseconds as an <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @return the port number as <tt>int</tt>.
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination port of this <tt>TCPMasterConnection</tt>. The
|
||||
* default is defined as <tt>Modbus.DEFAULT_PORT</tt>.
|
||||
*
|
||||
* @param port the port number as <tt>int</tt>.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination <tt>InetAddress</tt> of this
|
||||
* <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @return the destination address as <tt>InetAddress</tt>.
|
||||
*/
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination <tt>InetAddress</tt> of this
|
||||
* <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @param adr the destination address as <tt>InetAddress</tt>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <tt>TCPSlaveConnection</tt> 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 <tt>TCPSlaveConnection</tt> 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 <tt>TCPSlaveConnection</tt>.
|
||||
*/
|
||||
public void close() {
|
||||
if (connected) {
|
||||
try {
|
||||
transport.close();
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.warn("Could not close socket", ex);
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>ModbusTransport</tt> associated with this
|
||||
* <tt>TCPMasterConnection</tt>.
|
||||
*
|
||||
* @return the connection's <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public AbstractModbusTransport getModbusTransport() {
|
||||
return transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the associated <tt>ModbusTransport</tt> of this
|
||||
* <tt>TCPMasterConnection</tt> 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 <tt>TCPSlaveConnection</tt>.
|
||||
*
|
||||
* @return the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for this <tt>TCPSlaveConnection</tt>.
|
||||
*
|
||||
* @param timeout the timeout in milliseconds as <tt>int</tt>.
|
||||
*/
|
||||
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 <tt>TCPSlaveConnection</tt>.
|
||||
*
|
||||
* @return the port number as <tt>int</tt>.
|
||||
*/
|
||||
public int getPort() {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination <tt>InetAddress</tt> of this
|
||||
* <tt>TCPSlaveConnection</tt>.
|
||||
*
|
||||
* @return the destination address as <tt>InetAddress</tt>.
|
||||
*/
|
||||
public InetAddress getAddress() {
|
||||
return socket.getLocalAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this <tt>TCPSlaveConnection</tt> is connected.
|
||||
*
|
||||
* @return <tt>true</tt> if connected, <tt>false</tt> otherwise.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
}
|
@ -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 <tt>UDPMasterConnection</tt> instance
|
||||
* with a given destination address.
|
||||
*
|
||||
* @param adr the destination <tt>InetAddress</tt>.
|
||||
*/
|
||||
public UDPMasterConnection(InetAddress adr) {
|
||||
address = adr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @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 <tt>UDPMasterConnection</tt>.
|
||||
*/
|
||||
public void close() {
|
||||
if (connected) {
|
||||
try {
|
||||
terminal.deactivate();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.debug("Exception occurred while closing UDPMasterConnection", ex);
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <tt>ModbusTransport</tt> associated with this
|
||||
* <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @return the connection's <tt>ModbusTransport</tt>.
|
||||
*/
|
||||
public AbstractModbusTransport getModbusTransport() {
|
||||
return terminal == null ? null : terminal.getTransport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the terminal used for handling the package traffic.
|
||||
*
|
||||
* @return a <tt>UDPTerminal</tt> instance.
|
||||
*/
|
||||
public AbstractUDPTerminal getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timeout for this <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @return the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public synchronized int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout for this <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @param timeout the timeout as <tt>int</tt>.
|
||||
*/
|
||||
public synchronized void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
if (terminal != null) {
|
||||
terminal.setTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination port of this
|
||||
* <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @return the port number as <tt>int</tt>.
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination port of this
|
||||
* <tt>UDPMasterConnection</tt>.
|
||||
* The default is defined as <tt>Modbus.DEFAULT_PORT</tt>.
|
||||
*
|
||||
* @param port the port number as <tt>int</tt>.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the destination <tt>InetAddress</tt> of this
|
||||
* <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @return the destination address as <tt>InetAddress</tt>.
|
||||
*/
|
||||
public InetAddress getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination <tt>InetAddress</tt> of this
|
||||
* <tt>UDPMasterConnection</tt>.
|
||||
*
|
||||
* @param adr the destination address as <tt>InetAddress</tt>.
|
||||
*/
|
||||
public void setAddress(InetAddress adr) {
|
||||
address = adr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this <tt>UDPMasterConnection</tt> is connected.
|
||||
*
|
||||
* @return <tt>true</tt> if connected, <tt>false</tt> otherwise.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>UDPMasterTerminal</tt>.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>UDPSlaveTerminal</tt>.
|
||||
*
|
||||
* @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<Integer, DatagramPacket> requests = new Hashtable<Integer, DatagramPacket>(342);
|
||||
private LinkedBlockingQueue<byte[]> sendQueue = new LinkedBlockingQueue<byte[]>();
|
||||
private LinkedBlockingQueue<byte[]> receiveQueue = new LinkedBlockingQueue<byte[]>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 (<tt>byte[2]</tt>) 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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <tt>SimpleProcessImage</tt>
|
||||
* each time <tt>createProcessImageImplementation()</tt> 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 <tt>byte</tt>.
|
||||
* @param b2 the second <tt>byte</tt>.
|
||||
*
|
||||
* @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 <tt>byte</tt>.
|
||||
* @param b2 the second <tt>byte</tt>.
|
||||
*
|
||||
* @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 <tt>int</tt>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <p>
|
||||
* 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 <tt>DigitalIn</tt> is set.
|
||||
* <p>
|
||||
*
|
||||
* @return true if set, false otherwise.
|
||||
*/
|
||||
boolean isSet();
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user