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