diff --git a/build.gradle b/build.gradle index 1287c91..3181b66 100644 --- a/build.gradle +++ b/build.gradle @@ -6,15 +6,35 @@ buildscript { } dependencies { classpath "net.saliman:gradle-cobertura-plugin:2.2.8" + classpath group: "org.asciidoctor", name: "asciidoctorj-pdf", version: "1.5.0-alpha.11" + } } plugins { id "org.sonarqube" version "1.0" + id "org.asciidoctor.convert" version "1.5.3" } +apply plugin: "org.asciidoctor.convert" +apply plugin: "java" apply from: "configuration.gradle" +asciidoctor { + backends = ["pdf", "html5"] + attributes "stylesheet": "openmuc-asciidoc.css", + "toc2": "left", + "sampleSrc": file("src/sample/java"), + "pdf-stylesdir": "./", + "pdf-style": "pdf" + + resources { + from("$sourceDir") { + include "images/**" + } + } +} + configure(allprojects) { version = cfgVersion } @@ -122,8 +142,9 @@ configure(javaProjects) { } } } - - build.dependsOn(jarAll) + + build.dependsOn {asciidoctor} + build.dependsOn {jarAll} eclipse.pathVariables([GRADLE_USER_HOME:file(gradle.gradleUserHomeDir)]) tasks.eclipse.dependsOn(cleanEclipse) @@ -158,7 +179,7 @@ configure(repositoryProjects) { if (cfgSignPom) { signing { if ( project.hasProperty('signing.keyId') ) { - sign configurations.archives + sign configurations.archives } } } @@ -220,6 +241,8 @@ task javadocAll(type: Javadoc) { } exclude '**/internal/**' + exclude '**/java-gen/**' + exclude '**/app/**' destinationDir = new File(buildDir, 'docs/javadoc-all') @@ -267,6 +290,7 @@ tasks.withType(Tar) { dependsOn(writeSettings) dependsOn(distributionProjects.build) dependsOn(javadocAll) + dependsOn(asciidoctor) compression = Compression.GZIP diff --git a/configuration.gradle b/configuration.gradle index eccdc95..a82d388 100644 --- a/configuration.gradle +++ b/configuration.gradle @@ -1,7 +1,7 @@ project.ext { - cfgVersion = '1.0.0' + cfgVersion = '1.1.0' cfgGroup = 'org.openmuc' @@ -35,12 +35,12 @@ tasks.withType(Tar) { include 'build.gradle' include 'configuration.gradle' include 'license/**' - include 'doc/*.txt' - include 'doc/userguide/' + project.name + '-doc*.html' - include 'doc/userguide/' + project.name + '-doc_img/**' + include 'doc/CHANGELOG.txt' include 'run-scripts/**' + include 'gradle/wrapper/**' + include 'gradlew' + include 'gradlew.bat' include 'build/libs/**' - include 'build/docs/javadoc/**' include 'src/**' } @@ -49,6 +49,22 @@ tasks.withType(Tar) { } } + + into(project.name + '/doc/user-guide/') { + from('./build/asciidoc/html5/') { + include '**' + } + from('./build/asciidoc/pdf/') { + include '**' + } + } + + into(project.name + '/doc/') { + from('./build/docs/') { + include 'javadoc/**' + } + } + } @@ -87,4 +103,3 @@ uploadArchives { } } } - diff --git a/doc/CHANGELOG.txt b/doc/CHANGELOG.txt index 6dceb84..e52ee61 100644 --- a/doc/CHANGELOG.txt +++ b/doc/CHANGELOG.txt @@ -1,5 +1,14 @@ -v1.0 24-Feb-2016 ----------------- +v1.1.0 06-Jun-2016 +------------------ +- Renamed ClientSap to ClientConnectionBuilder and ServerSap to + Server.Builder +- fixed time conversion to ms of IeTime24 thanks to Jürgen Wieferink + from BTC AG +- added stopListening() to ServerSap allowing for clean close +- refactored console client + +v1.0.0 24-Feb-2016 +------------------ - fixed bug when creating multiple parallel connections using the same ClientSap - fixed a synchronization problem in the server implementation diff --git a/doc/user-guide/j60870-doc.html b/doc/user-guide/j60870-doc.html new file mode 100644 index 0000000..13e991b --- /dev/null +++ b/doc/user-guide/j60870-doc.html @@ -0,0 +1,581 @@ + + + + + + + +j60870 User Guide + + + + +
+
+

1. Intro

+
+
+

j60870 is an implementation of the IEC 60870-5-104 protocol standard +for client (i.e. master or controlling station) and server (i.e. slave +or controlled station) applications.

+
+
+

You can use j60870 to program your individual client or server +applications. A simple console client is part of the library. You can +execute it using the scripts found in the folder "run-scripts".

+
+
+
+
+

2. Distribution

+
+
+

After extracting the distribution tar file the j60870 library can be +found in the folder /build/libs.

+
+
+
+
+

3. Getting Started

+
+
+

3.1. Client

+
+

The easiest way to get started is by taking a look at the code of the +console client which can be found here: +src/main/java/org/openmuc/j60870/app/ConsoleClient.java . This +application in combination with the javadoc should satisfy most of +your documentation needs.

+
+
+

Here is a short summary of the steps to get a client running:

+
+
+
    +
  • +

    Create and configure an instance of ClientConnectionBuilder.

    +
  • +
  • +

    Connect to the server using ClientConnectionBuilder.connect() which +returns the connection. The client is now connected to the server +via TCP/IP.

    +
  • +
  • +

    Initialize the data transfer by calling +Connection.startDataTransfer.

    +
  • +
  • +

    Now all incoming ASDUs (except for confirmation messages) will be +forwarded to the ASduListener you registered. Every ASDU contains a +number of Information Objects. The information objects contain +information elements that make up the actual data. You will have to +cast the InformationElements of the ASDU to a concrete +implementation in order to access the data inside them. Every +standardized Information Element is implemented by a class starting +with the letters "Ie". The Type Identifier allows you to figure what +to cast a particular Information Element to.

    +
  • +
  • +

    You can use the Connection instance to send commands.

    +
  • +
+
+
+
+

3.2. Server

+
+

The easiest way to get started programming an IEC 60870-5-104 server +is by taking a look at the SampleServer located at: +src/sample/java/SampleServer.java . The easiest way to run the sample +server is by importing the project into Eclipse and run it from +there. This is explained here: +https://www.openmuc.org/faq/

+
+
+
+
+
+

4. Terminology

+
+
+
    +
  • +

    OA - Originator Address

    +
  • +
  • +

    Monitor direction - direction from server to client

    +
  • +
  • +

    Control direction - direction from client to server

    +
  • +
  • +

    CON - confirmation message

    +
  • +
  • +

    COT - Cause of transmission

    +
  • +
  • +

    STARTDT ACT - Start data tranfer message. Needs to be sent by the +client before information messages may be exchanged between client +and server.

    +
  • +
+
+
+
+
+

5. Develop j60870

+
+
+

We use the Gradle build automation tool. The distribution contains a +fully functional gradle build file ("build.gradle"). Thus if you +changed code and want to rebuild a library you can do it easily with +Gradle. Also if you want to import our software into Eclipse you can +easily create Eclipse project files using Gradle. Just follow the +instructions on our FAQ site: +https://www.openmuc.org/faq/

+
+
+
+
+

6. Authors

+
+
+

Developers:

+
+
+
    +
  • +

    Stefan Feuerhahn

    +
  • +
+
+
+
+
+ + + \ No newline at end of file diff --git a/doc/user-guide/j60870-doc.pdf b/doc/user-guide/j60870-doc.pdf new file mode 100644 index 0000000..02dd63a Binary files /dev/null and b/doc/user-guide/j60870-doc.pdf differ diff --git a/doc/userguide/j60870-doc-frames.html b/doc/userguide/j60870-doc-frames.html deleted file mode 100644 index 5cd4e0e..0000000 --- a/doc/userguide/j60870-doc-frames.html +++ /dev/null @@ -1,807 +0,0 @@ - - - - - -j60870 User Guide - - - - - -
-
-

1. Intro

-
-

j60870 is an implementation of the IEC 60870-5-104 protocol standard -for client (i.e. master or controlling station) and server (i.e. slave -or controlled station) applications.

-

You can use j60870 to program your individual client or server -applications. A simple command line ClientApp is part of the -library. You can execute it using the scripts found in the folder -"run-scripts".

-
-
-
-

2. Distribution

-
-

After extracting the distribution tar file the j60870 library can be -found in the folder /build/libs.

-

The javadoc for j60870 can be found in the folder build/docs/javadoc.

-
-
-
-

3. Getting Started

-
-
-

3.1. Client

-

The easiest way to get started is by taking a look at the code of the -ClientApp which can be found here: -src/main/java/org/openmuc/j60870/app/ClientApp.java . This application -in combination with the javadoc should satisfy most of your -documentation needs.

-

Here is a short summary of the steps to get a client running:

-
    -
  • -

    -Create an instance of ClientSap. -

    -
  • -
  • -

    -Configure the ClientSap. -

    -
  • -
  • -

    -Connect to the server using ClientSap.connect which returns the - connection. The client is now connected to the server via - TCP/IP. -

    -
  • -
  • -

    -Initialize the data transfer by calling - Connection.startDataTransfer. -

    -
  • -
  • -

    -Now all incoming ASDUs (except for confirmation messages) will be - forwarded to the ASduListener you registered. Every ASDU contains a - number of Information Objects. The information objects contain - information elements that make up the actual data. You will have to - cast the InformationElements of the ASDU to a concrete - implementation in order to access the data inside them. Every - standardized Information Element is implemented by a class starting - with the letters "Ie". The Type Identifier allows you to figure what - to cast a particular Information Element to. -

    -
  • -
  • -

    -You can use the Connection instance to send commands. -

    -
  • -
-
-
-

3.2. Server

-

The easiest way to get started programming an IEC 60870-5-104 server -is by taking a look at the SampleServer located at: -src/sample/java/SampleServer.java . The easiest way to run the sample -server is by importing the project into Eclipse and run it from -there. This is explained here: -http://www.openmuc.org/index.php?id=72#faq_gradle

-
-
-
-
-

4. Terminology

-
-
    -
  • -

    -OA - Originator Address -

    -
  • -
  • -

    -Monitor direction - direction from server to client -

    -
  • -
  • -

    -Control direction - direction from client to server -

    -
  • -
  • -

    -CON - confirmation message -

    -
  • -
  • -

    -COT - Cause of transmission -

    -
  • -
  • -

    -STARTDT ACT - Start data tranfer message. Needs to be sent by the - client before information messages may be exchanged between client - and server. -

    -
  • -
-
-
-
-

5. Develop j60870

-
-

We use the Gradle build automation tool. The distribution contains a -fully functional gradle build file ("build.gradle"). Thus if you -changed code and want to rebuild a library you can do it easily with -Gradle. Also if you want to import our software into Eclipse you can -easily create Eclipse project files using Gradle. Just follow the -instructions on our FAQ site: -http://www.openmuc.org/index.php?id=72#faq_gradle

-
-
-
-

6. Authors

-
-

Developers:

-
    -
  • -

    -Stefan Feuerhahn -

    -
  • -
-
-
-
-

- - - diff --git a/doc/userguide/j60870-doc.html b/doc/userguide/j60870-doc.html deleted file mode 100644 index 4a927fe..0000000 --- a/doc/userguide/j60870-doc.html +++ /dev/null @@ -1,773 +0,0 @@ - - - - - -j60870 User Guide - - - - - -
-
-

1. Intro

-
-

j60870 is an implementation of the IEC 60870-5-104 protocol standard -for client (i.e. master or controlling station) and server (i.e. slave -or controlled station) applications.

-

You can use j60870 to program your individual client or server -applications. A simple command line ClientApp is part of the -library. You can execute it using the scripts found in the folder -"run-scripts".

-
-
-
-

2. Distribution

-
-

After extracting the distribution tar file the j60870 library can be -found in the folder /build/libs.

-

The javadoc for j60870 can be found in the folder build/docs/javadoc.

-
-
-
-

3. Getting Started

-
-
-

3.1. Client

-

The easiest way to get started is by taking a look at the code of the -ClientApp which can be found here: -src/main/java/org/openmuc/j60870/app/ClientApp.java . This application -in combination with the javadoc should satisfy most of your -documentation needs.

-

Here is a short summary of the steps to get a client running:

-
    -
  • -

    -Create an instance of ClientSap. -

    -
  • -
  • -

    -Configure the ClientSap. -

    -
  • -
  • -

    -Connect to the server using ClientSap.connect which returns the - connection. The client is now connected to the server via - TCP/IP. -

    -
  • -
  • -

    -Initialize the data transfer by calling - Connection.startDataTransfer. -

    -
  • -
  • -

    -Now all incoming ASDUs (except for confirmation messages) will be - forwarded to the ASduListener you registered. Every ASDU contains a - number of Information Objects. The information objects contain - information elements that make up the actual data. You will have to - cast the InformationElements of the ASDU to a concrete - implementation in order to access the data inside them. Every - standardized Information Element is implemented by a class starting - with the letters "Ie". The Type Identifier allows you to figure what - to cast a particular Information Element to. -

    -
  • -
  • -

    -You can use the Connection instance to send commands. -

    -
  • -
-
-
-

3.2. Server

-

The easiest way to get started programming an IEC 60870-5-104 server -is by taking a look at the SampleServer located at: -src/sample/java/SampleServer.java . The easiest way to run the sample -server is by importing the project into Eclipse and run it from -there. This is explained here: -http://www.openmuc.org/index.php?id=72#faq_gradle

-
-
-
-
-

4. Terminology

-
-
    -
  • -

    -OA - Originator Address -

    -
  • -
  • -

    -Monitor direction - direction from server to client -

    -
  • -
  • -

    -Control direction - direction from client to server -

    -
  • -
  • -

    -CON - confirmation message -

    -
  • -
  • -

    -COT - Cause of transmission -

    -
  • -
  • -

    -STARTDT ACT - Start data tranfer message. Needs to be sent by the - client before information messages may be exchanged between client - and server. -

    -
  • -
-
-
-
-

5. Develop j60870

-
-

We use the Gradle build automation tool. The distribution contains a -fully functional gradle build file ("build.gradle"). Thus if you -changed code and want to rebuild a library you can do it easily with -Gradle. Also if you want to import our software into Eclipse you can -easily create Eclipse project files using Gradle. Just follow the -instructions on our FAQ site: -http://www.openmuc.org/index.php?id=72#faq_gradle

-
-
-
-

6. Authors

-
-

Developers:

-
    -
  • -

    -Stefan Feuerhahn -

    -
  • -
-
-
-
-

- - - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64..ca78035 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1372e29..1629bfd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/gradlew b/gradlew index 9b53f01..27309d9 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -112,8 +111,9 @@ fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then - APP_HOME=`cygpath --url --mixed "$APP_HOME"` - CLASSPATH=`cygpath --url --mixed "$CLASSPATH"` + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -134,7 +134,7 @@ if $cygwin ; then CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --url --ignore --mixed "$arg"` + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..832fdb6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/run-scripts/README.txt b/run-scripts/README.txt index 36833a3..49084d5 100644 --- a/run-scripts/README.txt +++ b/run-scripts/README.txt @@ -1,2 +1,3 @@ -In order to run the client app on Windows you have to rename j60870-clientapp.bat.winfile to j60870-clientapp.bat . +In order to run the client app on Windows you have to rename +j60870-console-client.bat.winfile to j60870-console-client.bat . diff --git a/run-scripts/j60870-clientapp.bat.winfile b/run-scripts/j60870-console-client.bat.winfile similarity index 52% rename from run-scripts/j60870-clientapp.bat.winfile rename to run-scripts/j60870-console-client.bat.winfile index e5bddca..0e7c22a 100644 --- a/run-scripts/j60870-clientapp.bat.winfile +++ b/run-scripts/j60870-console-client.bat.winfile @@ -3,4 +3,4 @@ set BATDIR=%~dp0 set LIBDIR=%BATDIR%..\build\libs -java -Djava.ext.dirs=%LIBDIR% org.openmuc.j60870.app.ClientApp %* +java -Djava.ext.dirs=%LIBDIR% org.openmuc.j60870.app.ConsoleClient %* diff --git a/run-scripts/j60870-clientapp.sh b/run-scripts/j60870-console-client.sh similarity index 87% rename from run-scripts/j60870-clientapp.sh rename to run-scripts/j60870-console-client.sh index 4631637..c5f2674 100644 --- a/run-scripts/j60870-clientapp.sh +++ b/run-scripts/j60870-console-client.sh @@ -1,7 +1,7 @@ #!/bin/bash JARS_LOCATION="../build/libs" -MAIN_CLASS="org.openmuc.j60870.app.ClientApp" +MAIN_CLASS="org.openmuc.j60870.app.ConsoleClient" SYSPROPS="" PARAMS="" diff --git a/src/docs/asciidoc/j60870-doc.adoc b/src/docs/asciidoc/j60870-doc.adoc new file mode 100644 index 0000000..9864fc2 --- /dev/null +++ b/src/docs/asciidoc/j60870-doc.adoc @@ -0,0 +1,95 @@ +j60870 User Guide +================= + +:numbered: + +== Intro + +j60870 is an implementation of the IEC 60870-5-104 protocol standard +for client (i.e. master or controlling station) and server (i.e. slave +or controlled station) applications. + +You can use j60870 to program your individual client or server +applications. A simple console client is part of the library. You can +execute it using the scripts found in the folder "run-scripts". + +== Distribution + +After extracting the distribution tar file the j60870 library can be +found in the folder /build/libs. + +== Getting Started + +=== Client + +The easiest way to get started is by taking a look at the code of the +console client which can be found here: +src/main/java/org/openmuc/j60870/app/ConsoleClient.java . This +application in combination with the javadoc should satisfy most of +your documentation needs. + +Here is a short summary of the steps to get a client running: + +* Create and configure an instance of ClientConnectionBuilder. + +* Connect to the server using ClientConnectionBuilder.connect() which + returns the connection. The client is now connected to the server + via TCP/IP. + +* Initialize the data transfer by calling + Connection.startDataTransfer. + +* Now all incoming ASDUs (except for confirmation messages) will be + forwarded to the ASduListener you registered. Every ASDU contains a + number of Information Objects. The information objects contain + information elements that make up the actual data. You will have to + cast the InformationElements of the ASDU to a concrete + implementation in order to access the data inside them. Every + standardized Information Element is implemented by a class starting + with the letters "Ie". The Type Identifier allows you to figure what + to cast a particular Information Element to. + +* You can use the Connection instance to send commands. + +=== Server + +The easiest way to get started programming an IEC 60870-5-104 server +is by taking a look at the SampleServer located at: +src/sample/java/SampleServer.java . The easiest way to run the sample +server is by importing the project into Eclipse and run it from +there. This is explained here: +https://www.openmuc.org/faq/ + +== Terminology + +* *OA* - Originator Address + +* *Monitor direction* - direction from server to client + +* *Control direction* - direction from client to server + +* *CON* - confirmation message + +* *COT* - Cause of transmission + +* *STARTDT ACT* - Start data tranfer message. Needs to be sent by the + client before information messages may be exchanged between client + and server. + +== Develop j60870 + +We use the Gradle build automation tool. The distribution contains a +fully functional gradle build file ("build.gradle"). Thus if you +changed code and want to rebuild a library you can do it easily with +Gradle. Also if you want to import our software into Eclipse you can +easily create Eclipse project files using Gradle. Just follow the +instructions on our FAQ site: +https://www.openmuc.org/faq/ + +== Authors + +Developers: + +* Stefan Feuerhahn + + diff --git a/src/docs/asciidoc/openmuc-asciidoc.css b/src/docs/asciidoc/openmuc-asciidoc.css new file mode 100644 index 0000000..33603b0 --- /dev/null +++ b/src/docs/asciidoc/openmuc-asciidoc.css @@ -0,0 +1,399 @@ +/* + * AsciiDoc 'volnitsky' theme for xhtml11 and html5 backends. + * Based on css from http://volnitsky.com, which was in turn based on default + * theme from AsciiDoc + * + * FIXME: The styling is still a bit rough in places. + * + */ + +/* Default font. */ +body { + font-family: Georgia,"Times New Roman",Times,serif; + +} + +/* Title font. */ + +h3,h4, h5, h6 { + color: #010101; + size:12px; +} + +#toc a { + border-bottom: 1px dotted #999999; + color: #04392C !important; + text-decoration: none !important; +} +#toc a:hover { + border-bottom: 1px solid #049674; + color: #049674 !important; + text-decoration: none !important; +} + +em { + font-style: italic; +/* color: #0D6C55;*/ + color: #4E4E4E; +} + +strong { + font-weight: bold; +/* color: #0D6C55;*/ + color: #000000; +} + + +div.sectionbody { + margin-left: 0; +} + +hr { + border: 1px solid #0D6C55; +} + + +pre { + padding: 0; + margin: 0; +} + +#author { + color: #0D6C55; + font-weight: bold; + font-size: 1.1em; +} + +#footer { + font-size: small; + padding-top: 0.5em; + margin-top: 4.0em; +} + +#footer-text { + float: left; + padding-bottom: 0.5em; +} + +#footer-badges { + float: right; + padding-bottom: 0.5em; +} + +#preamble { + margin-top: 1.5em; + margin-bottom: 1.5em; +} + +div.tableblock, div.imageblock, div.exampleblock, div.verseblock, +div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock, +div.admonitionblock { + margin-top: 1.5em; + margin-bottom: 1.5em; +} + +div.admonitionblock { + margin-top: 2.5em; + margin-bottom: 2.5em; +} + +div.content { /* Block element content. */ + padding: 0; + +} + +/* Block element titles. */ +div.title, caption.title { + color: #0D6C55; + font-weight: bold; + text-align: left; + margin-top: 1.0em; + margin-bottom: 0.5em; +} +div.title + * { + margin-top: 0; +} + +td div.title:first-child { + margin-top: 0.0em; +} +div.content div.title:first-child { + margin-top: 0.0em; +} +div.content + div.title { + margin-top: 0.0em; +} + +div.sidebarblock > div.content { + background: #ffffee; + border: 1px solid silver; + padding: 0.5em; +} + +div.listingblock > div.content { + border: 1px solid silver; + background: #f4f4f4; + padding: 0.5em; +} + +div.quoteblock { + padding-left: 2.0em; + margin-right: 10%; +} +div.quoteblock > div.attribution { + padding-top: 0.5em; + text-align: right; +} + +div.verseblock { + padding-left: 2.0em; + margin-right: 10%; +} +div.verseblock > pre.content { + font-family: inherit; +} +div.verseblock > div.attribution { + padding-top: 0.75em; + text-align: left; +} +/* DEPRECATED: Pre version 8.2.7 verse style literal block. */ +div.verseblock + div.attribution { + text-align: left; +} + +div.admonitionblock .icon { + vertical-align: top; + font-size: 1.1em; + font-weight: bold; + text-decoration: underline; + color: #0D6C55; + padding-right: 0.5em; +} +div.admonitionblock td.content { + padding-left: 0.5em; + border-left: 2px solid silver; +} + +div.exampleblock > div.content { + border-left: 2px solid silver; + padding: 0.5em; +} + +div.imageblock div.content { padding-left: 0; } +span.image img { border-style: none; } +a.image:visited { color: white; } + +dl { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +dt { + margin-top: 0.5em; + margin-bottom: 0; + font-style: normal; + color: #0D6C55; +} +dd > *:first-child { + margin-top: 0.1em; +} + +ul, ol { + list-style-position: outside; +} +ol.arabic { + list-style-type: decimal; +} +ol.loweralpha { + list-style-type: lower-alpha; +} +ol.upperalpha { + list-style-type: upper-alpha; +} +ol.lowerroman { + list-style-type: lower-roman; +} +ol.upperroman { + list-style-type: upper-roman; +} + +div.compact ul, div.compact ol, +div.compact p, div.compact p, +div.compact div, div.compact div { + margin-top: 0.1em; + margin-bottom: 0.1em; +} + +div.tableblock > table { + border: 3px solid #0D6C55; +} +thead { + font-weight: bold; + color: #0D6C55; +} +tfoot { + font-weight: bold; +} +td > div.verse { + white-space: pre; +} +p.table { + margin-top: 0; +} +/* Because the table frame attribute is overriden by CSS in most browsers. */ +div.tableblock > table[frame="void"] { + border-style: none; +} +div.tableblock > table[frame="hsides"] { + border-left-style: none; + border-right-style: none; +} +div.tableblock > table[frame="vsides"] { + border-top-style: none; + border-bottom-style: none; +} + + +div.hdlist { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +div.hdlist tr { + padding-bottom: 15px; +} +dt.hdlist1.strong, td.hdlist1.strong { + font-weight: bold; +} +td.hdlist1 { + vertical-align: top; + font-style: normal; + padding-right: 0.8em; + color: #0D6C55; +} +td.hdlist2 { + vertical-align: top; +} +div.hdlist.compact tr { + margin: 0; + padding-bottom: 0; +} + +.comment { + background: yellow; +} + +@media print { + #footer-badges { display: none; } +} + +#toctitle { + color: #049674; + font-size: 1.2em; + font-weight: bold; + margin-top: 1.0em; + margin-bottom: 0.1em; +} + +div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 { margin-top: 0; margin-bottom: 0; } +div.toclevel1 { margin-top: 0.3em; margin-left: 0; font-size: 1.0em; } +div.toclevel2 { margin-top: 0.25em; margin-left: 2em; font-size: 0.9em; } +div.toclevel3 { margin-left: 4em; font-size: 0.8em; } +div.toclevel4 { margin-left: 6em; font-size: 0.8em; } + + + +.monospaced, tt, div.listingblock > div.content { + font-family: Consolas, "Andale Mono", "Courier New", monospace; + color: #004400; + background: #f4f4f4; + max-width: 80em; + line-height: 1.2em; +} + +.paragraph p { + line-height: 1.5em; + margin-top: 1em; +} + +/*.paragraph p, li, dd, .content { max-width: 85em; } +.admonitionblock { max-width: 35em; }*/ + +div.sectionbody div.ulist > ul > li { + list-style-type: square; + color: #aaa; +} + div.sectionbody div.ulist > ul > li > * { + color: black; + /*font-size: 50%;*/ + } + + +div.sectionbody div.ulist > ul > li div.ulist > ul > li { + color: #ccd ; +} + div.sectionbody div.ulist > ul > li div.ulist > ul > li > * { + color: black ; + } + +/* + * html5 specific + * + * */ + +table.tableblock { + margin-top: 1.0em; + margin-bottom: 1.5em; +} +thead, p.tableblock.header { + font-weight: bold; + color: #049674; +} +p.tableblock { + margin-top: 0; +} +table.tableblock { + border-width: 3px; + border-spacing: 0px; + border-style: solid; + border-color: #0D6C55; + border-collapse: collapse; +} +th.tableblock, td.tableblock { + border-width: 1px; + padding: 4px; + border-style: solid; + border-color: #0D6C55; +} + +table.tableblock.frame-topbot { + border-left-style: hidden; + border-right-style: hidden; +} +table.tableblock.frame-sides { + border-top-style: hidden; + border-bottom-style: hidden; +} +table.tableblock.frame-none { + border-style: hidden; +} + +th.tableblock.halign-left, td.tableblock.halign-left { + text-align: left; +} +th.tableblock.halign-center, td.tableblock.halign-center { + text-align: center; +} +th.tableblock.halign-right, td.tableblock.halign-right { + text-align: right; +} + +th.tableblock.valign-top, td.tableblock.valign-top { + vertical-align: top; +} +th.tableblock.valign-middle, td.tableblock.valign-middle { + vertical-align: middle; +} +th.tableblock.valign-bottom, td.tableblock.valign-bottom { + vertical-align: bottom; +} + + diff --git a/src/docs/asciidoc/pdf-theme.yml b/src/docs/asciidoc/pdf-theme.yml new file mode 100644 index 0000000..f498497 --- /dev/null +++ b/src/docs/asciidoc/pdf-theme.yml @@ -0,0 +1,49 @@ +title_page: + align: right + +page: + layout: portrait +# margin: [0.75in, 1in, 0.75in, 1in] + size: A4 + +base: + font_family: Times-Roman + font_color: #333333 + font_size: 11 + line_height_length: 17 + line_height: $base_line_height_length / $base_font_size + +vertical_rhythm: $base_line_height_length + +heading: + font_color: #0000 + font_size: 14 + font_style: bold + line_height: 1.2 + margin_bottom: $vertical_rhythm + +link: + font_color: #939393 + +outline_list: + indent: $base_font_size * 1.5 + +header: + height: 0.75in + line_height: 1 + recto_content: + center: '{document-title}' + verso_content: + center: '{document-title}' + +footer: + height: 0.75in + line_height: 1 + recto_content: + right: '{chapter-title} | *{page-number}*' + verso_content: + left: '*{page-number}* | {chapter-title}' +caption: + align: center + font_color: #000000 + font_size: 9 \ No newline at end of file diff --git a/src/main/java/org/openmuc/j60870/APdu.java b/src/main/java/org/openmuc/j60870/APdu.java index 8337cc0..2e67326 100644 --- a/src/main/java/org/openmuc/j60870/APdu.java +++ b/src/main/java/org/openmuc/j60870/APdu.java @@ -23,6 +23,8 @@ import java.io.DataInputStream; import java.io.IOException; +import org.openmuc.j60870.internal.ConnectionSettings; + final class APdu { public enum APCI_TYPE { diff --git a/src/main/java/org/openmuc/j60870/ASdu.java b/src/main/java/org/openmuc/j60870/ASdu.java index 172d966..7189649 100644 --- a/src/main/java/org/openmuc/j60870/ASdu.java +++ b/src/main/java/org/openmuc/j60870/ASdu.java @@ -23,6 +23,8 @@ import java.io.DataInputStream; import java.io.IOException; +import org.openmuc.j60870.internal.ConnectionSettings; + /** * The application service data unit (ASDU). The ASDU is the payload of the application protocol data unit (APDU). Its * structure is defined in IEC 60870-5-101. The ASDU consists of the Data Unit Identifier and a number of Information diff --git a/src/main/java/org/openmuc/j60870/ClientConnectionBuilder.java b/src/main/java/org/openmuc/j60870/ClientConnectionBuilder.java new file mode 100644 index 0000000..7b66b57 --- /dev/null +++ b/src/main/java/org/openmuc/j60870/ClientConnectionBuilder.java @@ -0,0 +1,135 @@ +/* + * Copyright 2014-16 Fraunhofer ISE + * + * This file is part of j60870. + * For more information visit http://www.openmuc.org + * + * j60870 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * j60870 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with j60870. If not, see . + * + */ +package org.openmuc.j60870; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; + +/** + * The client connection builder is used to connect to IEC 60870-5-104 servers. A client application that wants to + * connect to a server should first create an instance of {@link ClientConnectionBuilder}. Next all the necessary + * configuration parameters can be set. Finally the {@link ClientConnectionBuilder#connect() connect} function is called + * to connect to the server. An instance of {@link ClientConnectionBuilder} can be used to create an unlimited number of + * connections. Changing the parameters of a {@link ClientConnectionBuilder} has no affect on connections that have + * already been created. + * + * Note that the configured lengths of the fields COT, CA and IOA have to be the same for all communicating nodes in a + * network. The default values used by {@link ClientConnectionBuilder} are those most commonly used in IEC 60870-5-104 + * communication. + * + * @author Stefan Feuerhahn + * + */ +public class ClientConnectionBuilder extends CommonBuilder { + + private SocketFactory socketFactory = SocketFactory.getDefault(); + private InetAddress address; + private int port = 2404; + InetAddress localAddr = null; + int localPort; + + /** + * Creates a client connection builder that can be used to connect to the given address. + * + * @param address + * the address to connect to + */ + public ClientConnectionBuilder(InetAddress address) { + this.address = address; + } + + /** + * Set the socket factory to used to create the socket for the connection. The default is + * {@link SocketFactory#getDefault()}. You could pass an {@link SSLSocketFactory} to enable SSL. + * + * @param socketFactory + * the socket factory + * @return this builder + */ + public ClientConnectionBuilder setSocketFactory(SocketFactory socketFactory) { + this.socketFactory = socketFactory; + return this; + } + + /** + * Sets the port to connect to. The default port is 2404. + * + * @param port + * the port to connect to. + * @return this builder + */ + public ClientConnectionBuilder setPort(int port) { + this.port = port; + return this; + } + + /** + * Sets the address to connect to. + * + * @param address + * the address to connect to. + * @return this builder + */ + public ClientConnectionBuilder setAddress(InetAddress address) { + this.address = address; + return this; + } + + /** + * Sets the local (client) address and port the socket will connect to. + * + * @param address + * the local address the socket is bound to, or null for any local address. + * @param port + * the local port the socket is bound to or zero for a system selected free port. + * @return this builder + */ + public ClientConnectionBuilder setLocalAddress(InetAddress address, int port) { + this.localAddr = address; + this.localPort = port; + return this; + } + + /** + * Connects to the server. The TCP/IP connection is build up and a {@link Connection} object is returned that can be + * used to communicate with the server. + * + * @return the {@link Connection} object that can be used to communicate with the server. + * @throws IOException + * if any kind of error occurs during connection build up. + */ + public Connection connect() throws IOException { + Socket socket; + if (localAddr == null) { + socket = socketFactory.createSocket(address, port); + } + else { + socket = socketFactory.createSocket(address, port, localAddr, localPort); + } + + return new Connection(socket, null, settings.getCopy()); + } + +} diff --git a/src/main/java/org/openmuc/j60870/ClientSap.java b/src/main/java/org/openmuc/j60870/ClientSap.java deleted file mode 100644 index 1a7a195..0000000 --- a/src/main/java/org/openmuc/j60870/ClientSap.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2014-16 Fraunhofer ISE - * - * This file is part of j60870. - * For more information visit http://www.openmuc.org - * - * j60870 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * j60870 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with j60870. If not, see . - * - */ -package org.openmuc.j60870; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; - -import javax.net.SocketFactory; - -/** - * The Client Service Access Point is used to connect to IEC 60870-5-104 servers. A client application that wants to - * connect to a server should first create an instance of ClientSap. Next all the necessary configuration parameters can - * be set. Finally the {@link ClientSap#connect(InetAddress) connect} function is called to connect to the server. An - * instance of ClientSap can be used to create an unlimited number of connections. Changing the parameters of a - * ClientSap has no affect on connections that have already been created. - * - * Note that the configured lengths of the fields COT, CA and IOA have to be the same for all communicating nodes in a - * network. The default values used by ClientSap are those most commonly used in IEC 60870-5-104 communication. - * - * @author Stefan Feuerhahn - * - */ -public class ClientSap { - - private final SocketFactory socketFactory; - - private final ConnectionSettings settings = new ConnectionSettings(); - - /** - * Use this constructor to create a default client SAP that uses SocketFactory.getDefault() as its - * SocketFactory. - */ - public ClientSap() { - socketFactory = SocketFactory.getDefault(); - } - - /** - * Use this constructor to create a client SAP that uses the given SocketFactory to connect to servers. - * You could pass an SSLSocketFactory to enable SSL. - * - * @param socketFactory - * the socket factory - */ - public ClientSap(SocketFactory socketFactory) { - this.socketFactory = socketFactory; - } - - /** - * Sets the message fragment timeout. This is the timeout that the socket timeout is set to after the first byte of - * a message has been received. A command function (e.g. - * {@link Connection#interrogation(int, CauseOfTransmission, IeQualifierOfInterrogation) interrogation}) will throw - * an IOException if the socket throws this timeout. In addition any ASDU listener will be notified of the - * IOException. Usually the connection cannot recover from this kind of error. - * - * @param timeout - * the timeout in milliseconds. The default is 5000. - */ - public void setMessageFragmentTimeout(int timeout) { - if (timeout < 0) { - throw new IllegalArgumentException("invalid message fragment timeout: " + timeout); - } - settings.messageFragmentTimeout = timeout; - } - - /** - * Sets the length of the Cause Of Transmission (COT) field of the ASDU. Allowed values are 1 or 2. The default is - * 2. - * - * @param length - * the length of the Cause Of Transmission field - */ - public void setCotFieldLength(int length) { - if (length != 1 && length != 2) { - throw new IllegalArgumentException("invalid length"); - } - settings.cotFieldLength = length; - } - - /** - * Sets the length of the Common Address (CA) field of the ASDU. Allowed values are 1 or 2. The default is 2. - * - * @param length - * the length of the Common Address (CA) field - */ - public void setCommonAddressFieldLength(int length) { - if (length != 1 && length != 2) { - throw new IllegalArgumentException("invalid length"); - } - settings.cotFieldLength = length; - } - - /** - * Sets the length of the Information Object Address (IOA) field of the ASDU. Allowed values are 1, 2 or 3. The - * default is 3. - * - * @param length - * the length of the Information Object Address field - */ - public void setIoaFieldLength(int length) { - if (length < 1 || length > 3) { - throw new IllegalArgumentException("invalid length: " + length); - } - settings.ioaFieldLength = length; - } - - /** - * Sets the maximum time in ms that no acknowledgement has been received (for I-Frames or Test-Frames) before - * actively closing the connection. This timeout is called t1 by the standard. Default is 15s, minimum is 1s, - * maximum is 255s. - * - * @param time - * the maximum time in ms that no acknowledgement has been received before actively closing the - * connection. - */ - public void setMaxTimeNoAckReceived(int time) { - if (time < 1000 || time > 255000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); - } - settings.maxTimeNoAckReceived = time; - } - - /** - * Sets the maximum time in ms before confirming received messages that have not yet been acknowledged using an S - * format APDU. This timeout is called t2 by the standard. Default is 10s, minimum is 1s, maximum is 255s. - * - * @param time - * the maximum time in ms before confirming received messages that have not yet been acknowledged using - * an S format APDU. - */ - public void setMaxTimeNoAckSent(int time) { - if (time < 1000 || time > 255000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); - } - settings.maxTimeNoAckSent = time; - } - - /** - * Sets the maximum time in ms that the connection may be idle before sending a test frame. This timeout is called - * t3 by the standard. Default is 20s, minimum is 1s, maximum is 172800s (48h). - * - * @param time - * the maximum time in ms that the connection may be idle before sending a test frame. - */ - public void setMaxIdleTime(int time) { - if (time < 1000 || time > 172800000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 172800000ms"); - } - settings.maxIdleTime = time; - } - - /** - * Sets the number of unacknowledged I format APDUs received before the connection will automatically send an S - * format APDU to confirm them. This parameter is called w by the standard. Default is 8, minimum is 1, maximum is - * 32767. - * - * @param maxNum - * the number of unacknowledged I format APDUs received before the connection will automatically send an - * S format APDU to confirm them. - */ - public void setMaxUnconfirmedIPdusReceived(int maxNum) { - if (maxNum < 1 || maxNum > 32767) { - throw new IllegalArgumentException("invalid maxNum: " + maxNum + ", must be a value between 1 and 32767"); - } - settings.maxUnconfirmedIPdusReceived = maxNum; - } - - /** - * Connects to the given address on port 2404. The TCP/IP connection is build up and a Connection object is returned - * that can be used to communicate with the server. - * - * @param address - * the address to connect to - * @return the ClientConnection object that can be used to communicate with the server. - * @throws IOException - * if any kind of error occurs during connection build up. - */ - public Connection connect(InetAddress address) throws IOException { - return connect(address, 2404, null, 0); - } - - /** - * Connects to the given address and port. The TCP/IP connection is build up and a Connection object is returned - * that can be used to communicate with the server. - * - * @param address - * the address to connect to - * @param port - * the port to connect to. The IEC 60870-5-104 standard specifies the use of port 2404. - * @return the ClientConnection object that can be used to communicate with the server. - * @throws IOException - * if any kind of error occurs during connection build up. - */ - public Connection connect(InetAddress address, int port) throws IOException { - - return connect(address, port, null, 0); - } - - /** - * Connects to the given address and port. The TCP/IP connection is build up and a Connection object is returned - * that can be used to communicate with the server. - * - * @param address - * the address to connect to - * @param port - * the port to connect to. The IEC 60870-5-104 standard specifies the use of port 2404. - * @param localAddr - * the local address the socket is bound to, or null for anyLocal address. - * @param localPort - * the local port the socket is bound to or zero for a system selected free port. - * @return the Connection object that can be used to communicate with the server. - * @throws IOException - * if any kind of error occurs during connection build up. - */ - public Connection connect(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException { - Socket socket; - if (localAddr == null) { - socket = socketFactory.createSocket(address, port); - } - else { - socket = socketFactory.createSocket(address, port, localAddr, localPort); - } - - Connection clientConnection = new Connection(socket, null, settings.getCopy()); - - return clientConnection; - } -} diff --git a/src/main/java/org/openmuc/j60870/CommonBuilder.java b/src/main/java/org/openmuc/j60870/CommonBuilder.java new file mode 100644 index 0000000..8e000bd --- /dev/null +++ b/src/main/java/org/openmuc/j60870/CommonBuilder.java @@ -0,0 +1,158 @@ +/* + * Copyright 2014-16 Fraunhofer ISE + * + * This file is part of j60870. + * For more information visit http://www.openmuc.org + * + * j60870 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * j60870 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with j60870. If not, see . + * + */ +package org.openmuc.j60870; + +import org.openmuc.j60870.internal.ConnectionSettings; + +class CommonBuilder> { + + final ConnectionSettings settings = new ConnectionSettings(); + + /** + * Access the casted this reference. + * + * @return the reference of the object. + */ + @SuppressWarnings("unchecked") + private T self() { + return (T) this; + } + + /** + * Sets the length of the Cause Of Transmission (COT) field of the ASDU. Allowed values are 1 or 2. The default is + * 2. + * + * @param length + * the length of the Cause Of Transmission field + * @return this builder + */ + public T setCotFieldLength(int length) { + if (length != 1 && length != 2) { + throw new IllegalArgumentException("invalid length"); + } + settings.cotFieldLength = length; + return self(); + } + + /** + * Sets the length of the Common Address (CA) field of the ASDU. Allowed values are 1 or 2. The default is 2. + * + * @param length + * the length of the Common Address (CA) field + * @return this builder + */ + public T setCommonAddressFieldLength(int length) { + if (length != 1 && length != 2) { + throw new IllegalArgumentException("invalid length"); + } + settings.cotFieldLength = length; + return self(); + } + + /** + * Sets the length of the Information Object Address (IOA) field of the ASDU. Allowed values are 1, 2 or 3. The + * default is 3. + * + * @param length + * the length of the Information Object Address field + * @return this builder + */ + public T setIoaFieldLength(int length) { + if (length < 1 || length > 3) { + throw new IllegalArgumentException("invalid length: " + length); + } + settings.ioaFieldLength = length; + return self(); + } + + /** + * Sets the maximum time in ms that no acknowledgement has been received (for I-Frames or Test-Frames) before + * actively closing the connection. This timeout is called t1 by the standard. Default is 15s, minimum is 1s, + * maximum is 255s. + * + * @param time + * the maximum time in ms that no acknowledgement has been received before actively closing the + * connection. + * @return this builder + */ + public T setMaxTimeNoAckReceived(int time) { + if (time < 1000 || time > 255000) { + throw new IllegalArgumentException( + "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); + } + settings.maxTimeNoAckReceived = time; + return self(); + } + + /** + * Sets the maximum time in ms before confirming received messages that have not yet been acknowledged using an S + * format APDU. This timeout is called t2 by the standard. Default is 10s, minimum is 1s, maximum is 255s. + * + * @param time + * the maximum time in ms before confirming received messages that have not yet been acknowledged using + * an S format APDU. + * @return this builder + */ + public T setMaxTimeNoAckSent(int time) { + if (time < 1000 || time > 255000) { + throw new IllegalArgumentException( + "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); + } + settings.maxTimeNoAckSent = time; + return self(); + } + + /** + * Sets the maximum time in ms that the connection may be idle before sending a test frame. This timeout is called + * t3 by the standard. Default is 20s, minimum is 1s, maximum is 172800s (48h). + * + * @param time + * the maximum time in ms that the connection may be idle before sending a test frame. + * @return this builder + */ + public T setMaxIdleTime(int time) { + if (time < 1000 || time > 172800000) { + throw new IllegalArgumentException( + "invalid timeout: " + time + ", time must be between 1000ms and 172800000ms"); + } + settings.maxIdleTime = time; + return self(); + } + + /** + * Sets the number of unacknowledged I format APDUs received before the connection will automatically send an S + * format APDU to confirm them. This parameter is called w by the standard. Default is 8, minimum is 1, maximum is + * 32767. + * + * @param maxNum + * the number of unacknowledged I format APDUs received before the connection will automatically send an + * S format APDU to confirm them. + * @return this builder + */ + public T setMaxUnconfirmedIPdusReceived(int maxNum) { + if (maxNum < 1 || maxNum > 32767) { + throw new IllegalArgumentException("invalid maxNum: " + maxNum + ", must be a value between 1 and 32767"); + } + settings.maxUnconfirmedIPdusReceived = maxNum; + return self(); + } + +} diff --git a/src/main/java/org/openmuc/j60870/Connection.java b/src/main/java/org/openmuc/j60870/Connection.java index 939eb3c..82d1f1e 100644 --- a/src/main/java/org/openmuc/j60870/Connection.java +++ b/src/main/java/org/openmuc/j60870/Connection.java @@ -36,20 +36,21 @@ import java.util.concurrent.TimeoutException; import org.openmuc.j60870.APdu.APCI_TYPE; +import org.openmuc.j60870.internal.ConnectionSettings; /** - * Represents a connection between a client and a server. It is created either through an instance of {@link ClientSap} - * or passed to {@link ServerSapListener}. Once it has been closed it cannot be opened again. A newly created connection - * has successfully build up a TCP/IP connection to the server. Before receiving ASDUs or sending commands one has to - * call {@link Connection#startDataTransfer(ConnectionEventListener, int)} or + * Represents a connection between a client and a server. It is created either through an instance of + * {@link ClientConnectionBuilder} or passed to {@link ServerEventListener}. Once it has been closed it cannot be opened + * again. A newly created connection has successfully build up a TCP/IP connection to the server. Before receiving ASDUs + * or sending commands one has to call {@link Connection#startDataTransfer(ConnectionEventListener, int)} or * {@link Connection#waitForStartDT(ConnectionEventListener, int)}. Afterwards incoming ASDUs are forwarded to the * {@link ConnectionEventListener}. Incoming ASDUs are queued so that {@link ConnectionEventListener#newASdu(ASdu)} is - * never called simultaneously for the same connection. Note that the ASduListener is not notified of incoming - * confirmation messages (CONs). + * never called simultaneously for the same connection. * * Connection offers a method for every possible command defined by IEC 60870 (e.g. singleCommand). Every command * function may throw an IOException indicating a fatal connection error. In this case the connection will be - * automatically closed and a new connection will have to be built up. + * automatically closed and a new connection will have to be built up. The command methods do not wait for an + * acknowledgment but return right after the command has been sent. * * @author Stefan Feuerhahn * @@ -697,7 +698,7 @@ public void regulatingStepCommand(int commonAddress, CauseOfTransmission cot, in */ public void regulatingStepCommandWithTimeTag(int commonAddress, CauseOfTransmission cot, int informationObjectAddress, IeRegulatingStepCommand regulatingStepCommand, IeTime56 timeTag) - throws IOException { + throws IOException { ASdu aSdu = new ASdu(TypeId.C_RC_TA_1, false, cot, false, false, originatorAddress, commonAddress, new InformationObject[] { new InformationObject(informationObjectAddress, @@ -1157,7 +1158,7 @@ public void fileReady(int commonAddress, int informationObjectAddress, IeNameOfF public void sectionReady(int commonAddress, int informationObjectAddress, IeNameOfFile nameOfFile, IeNameOfSection nameOfSection, IeLengthOfFileOrSection lengthOfSection, IeSectionReadyQualifier qualifier) - throws IOException { + throws IOException { ASdu aSdu = new ASdu(TypeId.F_SR_NA_1, false, CauseOfTransmission.FILE_TRANSFER, false, false, originatorAddress, commonAddress, new InformationObject[] { new InformationObject(informationObjectAddress, @@ -1167,7 +1168,7 @@ public void sectionReady(int commonAddress, int informationObjectAddress, IeName public void callOrSelectFiles(int commonAddress, CauseOfTransmission cot, int informationObjectAddress, IeNameOfFile nameOfFile, IeNameOfSection nameOfSection, IeSelectAndCallQualifier qualifier) - throws IOException { + throws IOException { ASdu aSdu = new ASdu(TypeId.F_SC_NA_1, false, cot, false, false, originatorAddress, commonAddress, new InformationObject[] { new InformationObject(informationObjectAddress, new InformationElement[][] { { nameOfFile, nameOfSection, qualifier } }) }); @@ -1176,7 +1177,7 @@ public void callOrSelectFiles(int commonAddress, CauseOfTransmission cot, int in public void lastSectionOrSegment(int commonAddress, int informationObjectAddress, IeNameOfFile nameOfFile, IeNameOfSection nameOfSection, IeLastSectionOrSegmentQualifier qualifier, IeChecksum checksum) - throws IOException { + throws IOException { ASdu aSdu = new ASdu(TypeId.F_LS_NA_1, false, CauseOfTransmission.FILE_TRANSFER, false, false, originatorAddress, commonAddress, new InformationObject[] { new InformationObject(informationObjectAddress, diff --git a/src/main/java/org/openmuc/j60870/IeTime24.java b/src/main/java/org/openmuc/j60870/IeTime24.java index b94bd84..fc35bae 100644 --- a/src/main/java/org/openmuc/j60870/IeTime24.java +++ b/src/main/java/org/openmuc/j60870/IeTime24.java @@ -64,7 +64,7 @@ int encode(byte[] buffer, int i) { } public int getTimeInMs() { - return (value[0] & 0xff) + ((value[1] & 0xff) << 8) + value[2] * 6000; + return (value[0] & 0xff) + ((value[1] & 0xff) << 8) + value[2] * 60000; } @Override diff --git a/src/main/java/org/openmuc/j60870/InformationObject.java b/src/main/java/org/openmuc/j60870/InformationObject.java index 8ed6085..24296ec 100644 --- a/src/main/java/org/openmuc/j60870/InformationObject.java +++ b/src/main/java/org/openmuc/j60870/InformationObject.java @@ -23,6 +23,8 @@ import java.io.DataInputStream; import java.io.IOException; +import org.openmuc.j60870.internal.ConnectionSettings; + /** * Every Information Object contains: *
    diff --git a/src/main/java/org/openmuc/j60870/Server.java b/src/main/java/org/openmuc/j60870/Server.java new file mode 100644 index 0000000..6ea5a2e --- /dev/null +++ b/src/main/java/org/openmuc/j60870/Server.java @@ -0,0 +1,159 @@ +/* + * Copyright 2014-16 Fraunhofer ISE + * + * This file is part of j60870. + * For more information visit http://www.openmuc.org + * + * j60870 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * j60870 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with j60870. If not, see . + * + */ +package org.openmuc.j60870; + +import java.io.IOException; +import java.net.InetAddress; + +import javax.net.ServerSocketFactory; + +import org.openmuc.j60870.internal.ConnectionSettings; + +/** + * The server is used to start listening for IEC 60870-5-104 client connections. + * + * @author Stefan Feuerhahn + * + */ +public class Server { + + private ServerThread serverThread; + + private final int port; + private final InetAddress bindAddr; + private final int backlog; + private final ServerSocketFactory serverSocketFactory; + private final int maxConnections; + + private final ConnectionSettings settings; + + public static class Builder extends CommonBuilder { + + private int port = 2404; + private InetAddress bindAddr = null; + private int backlog = 0; + private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault(); + + private int maxConnections = 100; + + /** + * Sets the TCP port that the server will listen on. IEC 60870-5-104 usually uses port 2404. + * + * @param port + * the port + * @return this builder + */ + public Builder setPort(int port) { + this.port = port; + return this; + } + + /** + * Sets the backlog that is passed to the java.net.ServerSocket. + * + * @param backlog + * the backlog + * @return this builder + */ + public Builder setBacklog(int backlog) { + this.backlog = backlog; + return this; + } + + /** + * Sets the IP address to bind to. It is passed to java.net.ServerSocket + * + * @param bindAddr + * the IP address to bind to + * @return this builder + */ + public Builder setBindAddr(InetAddress bindAddr) { + this.bindAddr = bindAddr; + return this; + } + + /** + * Sets the ServerSocketFactory to be used to create the ServerSocket. Default is + * ServerSocketFactory.getDefault(). + * + * @param socketFactory + * the ServerSocketFactory to be used to create the ServerSocket + * @return this builder + */ + public Builder setSocketFactory(ServerSocketFactory socketFactory) { + this.serverSocketFactory = socketFactory; + return this; + } + + /** + * Set the maximum number of client connections that are allowed in parallel. + * + * @param maxConnections + * the number of connections allowed (default is 100) @ return this builder + * @return this builder + */ + public Builder setMaxConnections(int maxConnections) { + if (maxConnections <= 0) { + throw new IllegalArgumentException("maxConnections is out of bound"); + } + this.maxConnections = maxConnections; + return this; + } + + public Server build() { + return new Server(this); + } + + } + + private Server(Builder builder) { + port = builder.port; + bindAddr = builder.bindAddr; + backlog = builder.backlog; + serverSocketFactory = builder.serverSocketFactory; + maxConnections = builder.maxConnections; + settings = builder.settings.getCopy(); + } + + /** + * Starts a new thread that listens on the configured port. This method is non-blocking. + * + * @param listener + * the ServerConnectionListener that will be notified when remote clients are connecting or the server + * stopped listening. + * @throws IOException + * if any kind of error occures while creating the server socket. + */ + public void start(ServerEventListener listener) throws IOException { + serverThread = new ServerThread(serverSocketFactory.createServerSocket(port, backlog, bindAddr), settings, + maxConnections, listener); + serverThread.start(); + } + + /** + * Stop listening for new connections. Existing connections are not touched. + */ + public void stop() { + serverThread.stopServer(); + serverThread = null; + } + +} diff --git a/src/main/java/org/openmuc/j60870/ServerSapListener.java b/src/main/java/org/openmuc/j60870/ServerEventListener.java similarity index 97% rename from src/main/java/org/openmuc/j60870/ServerSapListener.java rename to src/main/java/org/openmuc/j60870/ServerEventListener.java index 6d00bd2..5c9a9cc 100644 --- a/src/main/java/org/openmuc/j60870/ServerSapListener.java +++ b/src/main/java/org/openmuc/j60870/ServerEventListener.java @@ -22,7 +22,7 @@ import java.io.IOException; -public interface ServerSapListener { +public interface ServerEventListener { public void connectionIndication(Connection connection); diff --git a/src/main/java/org/openmuc/j60870/ServerSap.java b/src/main/java/org/openmuc/j60870/ServerSap.java deleted file mode 100644 index 9f654d7..0000000 --- a/src/main/java/org/openmuc/j60870/ServerSap.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2014-16 Fraunhofer ISE - * - * This file is part of j60870. - * For more information visit http://www.openmuc.org - * - * j60870 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * j60870 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with j60870. If not, see . - * - */ -package org.openmuc.j60870; - -import java.io.IOException; -import java.net.InetAddress; - -import javax.net.ServerSocketFactory; - -/** - * The Server Service Access Point is used to start listening for IEC 60870-5-104 client connections. - * - * @author Stefan Feuerhahn - * - */ -public class ServerSap { - - private ServerThread serverThread; - - private final int port; - private final InetAddress bindAddr; - private final int backlog; - private final ServerSapListener connectionListener; - private final ServerSocketFactory serverSocketFactory; - - private final ConnectionSettings settings = new ConnectionSettings(); - private int maxConnections = 100; - - /** - * Use this constructor to create a ServerSAP that listens on port 2404 using the default ServerSocketFactory. - * - * @param connectionListener - * the ServerConnectionListener that will be notified when remote clients are connecting or the server - * stopped listening. - */ - public ServerSap(ServerSapListener connectionListener) { - this(2404, 0, null, ServerSocketFactory.getDefault(), connectionListener); - } - - /** - * Use this constructor to create a ServerSAP that listens on the given port using the default ServerSocketFactory. - * - * @param port - * the TCP port that the server will listen on. IEC 60870-5-104 usually uses port 2404. - * @param connectionListener - * the ServerConnectionListener that will be notified when remote clients are connecting or the server - * stopped listening. - */ - public ServerSap(int port, ServerSapListener connectionListener) { - this(port, 0, null, ServerSocketFactory.getDefault(), connectionListener); - } - - /** - * Use this constructor to create a ServerSAP that can listen on a port with a specified ServerSocketFactory. - * - * @param port - * the TCP port that the server will listen on. IEC 60870-5-104 usually uses port 2404. - * @param backlog - * is passed to the java.net.ServerSocket - * @param bindAddr - * the IP address to bind to. It is passed to java.net.ServerSocket - * @param serverSocketFactory - * The ServerSocketFactory to be used to create the ServerSocket - * @param connectionListener - * the ServerConnectionListener that will be notified when remote clients are connecting or the server - * stopped listening. - */ - public ServerSap(int port, int backlog, InetAddress bindAddr, ServerSocketFactory serverSocketFactory, - ServerSapListener connectionListener) { - - this.port = port; - this.backlog = backlog; - this.bindAddr = bindAddr; - this.connectionListener = connectionListener; - this.serverSocketFactory = serverSocketFactory; - } - - /** - * Sets the message fragment timeout. This is the timeout that the socket timeout is set to after the first byte of - * a message has been received. A command function (e.g. - * {@link Connection#interrogation(int, CauseOfTransmission, IeQualifierOfInterrogation) interrogation}) will throw - * an IOException if the socket throws this timeout. In addition any ASDU listener will be notified of the - * IOException. Usually the connection cannot recover from this kind of error. - * - * @param timeout - * the timeout in milliseconds. The default is 5000. - */ - public void setMessageFragmentTimeout(int timeout) { - if (timeout < 0) { - throw new IllegalArgumentException("invalid message fragment timeout: " + timeout); - } - settings.messageFragmentTimeout = timeout; - } - - /** - * Sets the length of the Cause Of Transmission (COT) field of the ASDU. Allowed values are 1 or 2. The default is - * 2. - * - * @param length - * the length of the Cause Of Transmission field - */ - public void setCotFieldLength(int length) { - if (length != 1 && length != 2) { - throw new IllegalArgumentException("invalid length"); - } - settings.cotFieldLength = length; - } - - /** - * Sets the length of the Common Address (CA) field of the ASDU. Allowed values are 1 or 2. The default is 2. - * - * @param length - * the length of the Common Address (CA) field - */ - public void setCommonAddressFieldLength(int length) { - if (length != 1 && length != 2) { - throw new IllegalArgumentException("invalid length"); - } - settings.cotFieldLength = length; - } - - /** - * Sets the length of the Information Object Address (IOA) field of the ASDU. Allowed values are 1, 2 or 3. The - * default is 3. - * - * @param length - * the length of the Information Object Address field - */ - public void setIoaFieldLength(int length) { - if (length < 1 || length > 3) { - throw new IllegalArgumentException("invalid length: " + length); - } - settings.ioaFieldLength = length; - } - - /** - * Sets the maximum time in ms that no acknowledgement has been received (for I-Frames or Test-Frames) before - * actively closing the connection. This timeout is called t1 by the standard. Default is 15s, minimum is 1s, - * maximum is 255s. - * - * @param time - * the maximum time in ms that no acknowledgement has been received before actively closing the - * connection. - */ - public void setMaxTimeNoAckReceived(int time) { - if (time < 1000 || time > 255000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); - } - settings.maxTimeNoAckReceived = time; - } - - /** - * Sets the maximum time in ms before confirming received messages that have not yet been acknowledged using an S - * format APDU. This timeout is called t2 by the standard. Default is 10s, minimum is 1s, maximum is 255s. - * - * @param time - * the maximum time in ms before confirming received messages that have not yet been acknowledged using - * an S format APDU. - */ - public void setMaxTimeNoAckSent(int time) { - if (time < 1000 || time > 255000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 255000ms"); - } - settings.maxTimeNoAckSent = time; - } - - /** - * Sets the maximum time in ms that the connection may be idle before sending a test frame. This timeout is called - * t3 by the standard. Default is 20s, minimum is 1s, maximum is 172800s (48h). - * - * @param time - * the maximum time in ms that the connection may be idle before sending a test frame. - */ - public void setMaxIdleTime(int time) { - if (time < 1000 || time > 172800000) { - throw new IllegalArgumentException( - "invalid timeout: " + time + ", time must be between 1000ms and 172800000ms"); - } - settings.maxIdleTime = time; - } - - /** - * Sets the number of unacknowledged I format APDUs received before the connection will automatically send an S - * format APDU to confirm them. This parameter is called w by the standard. Default is 8, minimum is 1, maximum is - * 32767. - * - * @param maxNum - * the number of unacknowledged I format APDUs received before the connection will automatically send an - * S format APDU to confirm them. - */ - public void setMaxUnconfirmedIPdusReceived(int maxNum) { - if (maxNum < 1 || maxNum > 32767) { - throw new IllegalArgumentException("invalid maxNum: " + maxNum + ", must be a value between 1 and 32767"); - } - settings.maxUnconfirmedIPdusReceived = maxNum; - } - - /** - * Set the maximum number of client connections that are allowed in parallel. - * - * @param maxConnections - * the number of connections allowed (default is 100) - */ - public void setMaxConnections(int maxConnections) { - if (maxConnections <= 0) { - throw new IllegalArgumentException("maxConnections is out of bound"); - } - this.maxConnections = maxConnections; - } - - /** - * Starts a new thread that listens on the configured port. This method is non-blocking. - * - * @throws IOException - * if any kind of error occures while creating the server socket. - */ - public void startListening() throws IOException { - serverThread = new ServerThread(serverSocketFactory.createServerSocket(port, backlog, bindAddr), - settings.getCopy(), maxConnections, connectionListener); - serverThread.start(); - } - -} diff --git a/src/main/java/org/openmuc/j60870/ServerThread.java b/src/main/java/org/openmuc/j60870/ServerThread.java index 7fb8e2a..e2aef8b 100644 --- a/src/main/java/org/openmuc/j60870/ServerThread.java +++ b/src/main/java/org/openmuc/j60870/ServerThread.java @@ -26,18 +26,20 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.openmuc.j60870.internal.ConnectionSettings; + final class ServerThread extends Thread { private final ServerSocket serverSocket; private final ConnectionSettings settings; private final int maxConnections; - private final ServerSapListener serverSapListener; + private final ServerEventListener serverSapListener; private boolean stopServer = false; private int numConnections = 0; ServerThread(ServerSocket serverSocket, ConnectionSettings settings, int maxConnections, - ServerSapListener serverSapListener) { + ServerEventListener serverSapListener) { this.serverSocket = serverSocket; this.settings = settings; this.maxConnections = maxConnections; diff --git a/src/main/java/org/openmuc/j60870/app/ClientApp.java b/src/main/java/org/openmuc/j60870/app/ClientApp.java deleted file mode 100644 index e2b3cda..0000000 --- a/src/main/java/org/openmuc/j60870/app/ClientApp.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2014-16 Fraunhofer ISE - * - * This file is part of j60870. - * For more information visit http://www.openmuc.org - * - * j60870 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * j60870 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with j60870. If not, see . - * - */ -package org.openmuc.j60870.app; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.concurrent.TimeoutException; - -import org.openmuc.j60870.ASdu; -import org.openmuc.j60870.CauseOfTransmission; -import org.openmuc.j60870.ClientSap; -import org.openmuc.j60870.Connection; -import org.openmuc.j60870.ConnectionEventListener; -import org.openmuc.j60870.IeQualifierOfInterrogation; -import org.openmuc.j60870.IeTime56; - -public final class ClientApp implements ConnectionEventListener { - - private static Connection clientConnection; - - private final BufferedReader is; - - public ClientApp(BufferedReader is) { - this.is = is; - } - - private static void printUsage() { - System.out.println("SYNOPSIS\n\torg.openmuc.j60870.app.ClientApp [-p ] [-ca ]"); - System.out.println("DESCRIPTION\n\tA client/master application to access IEC 60870-5-104 servers/slaves."); - System.out.println("OPTIONS"); - System.out.println("\t\n\t The address of the slave you want to access.\n"); - System.out.println("\t-p \n\t The port to connect to. The default port is 2404.\n"); - System.out.println( - "\t-ca \n\t The address of the target station or the broad cast address. The default is 1.\n"); - } - - public static void main(String[] args) { - if (args.length < 1 || args.length > 5) { - printUsage(); - return; - } - - String remoteHost = args[0]; - - int port = 2404; - int commonAddress = 1; - - for (int i = 0; i < args.length; i++) { - if (args[i].equals("-p")) { - i++; - if (i == args.length) { - printUsage(); - System.exit(1); - } - try { - port = Integer.parseInt(args[i]); - } catch (NumberFormatException e) { - printUsage(); - System.exit(1); - } - } - else if (args[i].equals("-ca")) { - i++; - if (i == args.length) { - printUsage(); - System.exit(1); - } - try { - commonAddress = Integer.parseInt(args[i]); - } catch (NumberFormatException e) { - printUsage(); - System.exit(1); - } - } - else { - remoteHost = args[i]; - } - } - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - if (clientConnection != null) { - clientConnection.close(); - } - } - }); - - ClientSap clientSap = new ClientSap(); - - InetAddress address; - try { - address = InetAddress.getByName(remoteHost); - } catch (UnknownHostException e) { - System.out.println("Unknown host: " + remoteHost); - return; - } - - try { - clientConnection = clientSap.connect(address, port); - } catch (IOException e) { - System.out.println("Unable to connect to remote host: " + remoteHost + "."); - return; - } - - BufferedReader is = new BufferedReader(new InputStreamReader(System.in)); - - try { - try { - clientConnection.startDataTransfer(new ClientApp(is), 5000); - } catch (TimeoutException e2) { - throw new IOException("starting data transfer timed out."); - } - System.out.println("successfully connected"); - - String line; - while (true) { - line = is.readLine(); - - if (line == null) { - throw new IOException("InputStream unexpectedly reached end of stream."); - } - else if (line.equals("?")) { - printHelp(); - } - else if (line.equals("q")) { - return; - } - else if (line.equals("1")) { - clientConnection.interrogation(commonAddress, CauseOfTransmission.ACTIVATION, - new IeQualifierOfInterrogation(0)); - } - else if (line.equals("2")) { - clientConnection.synchronizeClocks(commonAddress, new IeTime56(System.currentTimeMillis())); - } - else { - System.out.println("Unknown command, enter \'?\' for help"); - } - } - - } catch (IOException e) { - System.out.println("Connection closed for the following reason: " + e.getMessage()); - return; - } finally { - clientConnection.close(); - } - - } - - private static void printHelp() { - System.out.println("(1) Interrogation C_IC_NA_1\n(2) Synchronize clocks C_CS_NA_1"); - } - - @Override - public void newASdu(ASdu aSdu) { - System.out.println("\nReceived ASDU:\n" + aSdu); - - } - - @Override - public void connectionClosed(IOException e) { - System.out.print("Received connection closed signal. Reason: "); - if (!e.getMessage().isEmpty()) { - System.out.println(e.getMessage()); - } - else { - System.out.println("unknown"); - } - - try { - is.close(); - } catch (IOException e1) { - } - } - -} diff --git a/src/main/java/org/openmuc/j60870/app/ConsoleClient.java b/src/main/java/org/openmuc/j60870/app/ConsoleClient.java new file mode 100644 index 0000000..1044dfc --- /dev/null +++ b/src/main/java/org/openmuc/j60870/app/ConsoleClient.java @@ -0,0 +1,187 @@ +/* + * Copyright 2014-16 Fraunhofer ISE + * + * This file is part of j60870. + * For more information visit http://www.openmuc.org + * + * j60870 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * j60870 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with j60870. If not, see . + * + */ +package org.openmuc.j60870.app; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.openmuc.j60870.ASdu; +import org.openmuc.j60870.CauseOfTransmission; +import org.openmuc.j60870.ClientConnectionBuilder; +import org.openmuc.j60870.Connection; +import org.openmuc.j60870.ConnectionEventListener; +import org.openmuc.j60870.IeQualifierOfInterrogation; +import org.openmuc.j60870.IeTime56; +import org.openmuc.j60870.internal.cli.CliParameter; +import org.openmuc.j60870.internal.cli.CliParameterBuilder; +import org.openmuc.j60870.internal.cli.CliParseException; +import org.openmuc.j60870.internal.cli.CliParser; +import org.openmuc.j60870.internal.cli.IntCliParameter; +import org.openmuc.j60870.internal.cli.StringCliParameter; + +public final class ConsoleClient { + + private final StringCliParameter host = new CliParameterBuilder("-h") + .setDescription("The IP/domain address of the server you want to access.") + .setMandatory() + .buildStringParameter("host"); + private final IntCliParameter port = new CliParameterBuilder("-p").setDescription("The port to connect to.") + .buildIntegerParameter("port", 2404); + private final IntCliParameter commonAddress = new CliParameterBuilder("-ca") + .setDescription("The address of the target station or the broad cast address.") + .buildIntegerParameter("common_address", 1); + + private class ClientEventListener implements ConnectionEventListener { + + @Override + public void newASdu(ASdu aSdu) { + System.out.println("\nReceived ASDU:\n" + aSdu); + + } + + @Override + public void connectionClosed(IOException e) { + System.out.print("Received connection closed signal. Reason: "); + if (!e.getMessage().isEmpty()) { + System.out.println(e.getMessage()); + } + else { + System.out.println("unknown"); + } + + try { + is.close(); + } catch (IOException e1) { + } + } + + } + + private volatile Connection clientConnection; + + private BufferedReader is; + + public static void main(String[] args) { + new ConsoleClient(args).start(); + } + + public ConsoleClient(String[] args) { + List cliParameters = new ArrayList(); + cliParameters.add(host); + cliParameters.add(port); + cliParameters.add(commonAddress); + + CliParser cliParser = new CliParser("j60870-console-client", + "A client/master application to access IEC 60870-5-104 servers/slaves."); + cliParser.addParameters(cliParameters); + + try { + cliParser.parseArguments(args); + } catch (CliParseException e1) { + System.err.println("Error parsing command line parameters: " + e1.getMessage()); + System.out.println(cliParser.getUsageString()); + System.exit(1); + } + + } + + public void start() { + + InetAddress address; + try { + address = InetAddress.getByName(host.getValue()); + } catch (UnknownHostException e) { + System.out.println("Unknown host: " + host.getValue()); + return; + } + + ClientConnectionBuilder clientConnectionBuilder = new ClientConnectionBuilder(address).setPort(port.getValue()); + + try { + clientConnection = clientConnectionBuilder.connect(); + } catch (IOException e) { + System.out.println("Unable to connect to remote host: " + host.getValue() + "."); + return; + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + clientConnection.close(); + } + }); + + is = new BufferedReader(new InputStreamReader(System.in)); + + try { + try { + clientConnection.startDataTransfer(new ClientEventListener(), 5000); + } catch (TimeoutException e2) { + throw new IOException("starting data transfer timed out."); + } + System.out.println("successfully connected"); + + String line; + while (true) { + line = is.readLine(); + + if (line == null) { + throw new IOException("InputStream unexpectedly reached end of stream."); + } + else if (line.equals("?")) { + printHelp(); + } + else if (line.equals("q")) { + return; + } + else if (line.equals("1")) { + clientConnection.interrogation(commonAddress.getValue(), CauseOfTransmission.ACTIVATION, + new IeQualifierOfInterrogation(20)); + } + else if (line.equals("2")) { + clientConnection.synchronizeClocks(commonAddress.getValue(), + new IeTime56(System.currentTimeMillis())); + } + else { + System.out.println("Unknown command, enter \'?\' for help"); + } + } + + } catch (IOException e) { + System.out.println("Connection closed for the following reason: " + e.getMessage()); + return; + } finally { + clientConnection.close(); + } + + } + + private static void printHelp() { + System.out.println("(1) Interrogation C_IC_NA_1\n(2) Synchronize clocks C_CS_NA_1\n(q) Quit"); + } + +} diff --git a/src/main/java/org/openmuc/j60870/ConnectionSettings.java b/src/main/java/org/openmuc/j60870/internal/ConnectionSettings.java similarity index 95% rename from src/main/java/org/openmuc/j60870/ConnectionSettings.java rename to src/main/java/org/openmuc/j60870/internal/ConnectionSettings.java index 0920f2e..a71ed83 100644 --- a/src/main/java/org/openmuc/j60870/ConnectionSettings.java +++ b/src/main/java/org/openmuc/j60870/internal/ConnectionSettings.java @@ -18,9 +18,9 @@ * along with j60870. If not, see . * */ -package org.openmuc.j60870; +package org.openmuc.j60870.internal; -final class ConnectionSettings { +public final class ConnectionSettings { public int messageFragmentTimeout = 5000; diff --git a/src/main/java/org/openmuc/j60870/internal/cli/CliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/CliParameter.java new file mode 100644 index 0000000..9fa91e5 --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/CliParameter.java @@ -0,0 +1,47 @@ +package org.openmuc.j60870.internal.cli; + +public abstract class CliParameter { + + String name; + String description; + boolean optional; + boolean selected; + + CliParameter(CliParameterBuilder builder) { + name = builder.name; + description = builder.description; + optional = builder.optional; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * @return the optional + */ + public boolean isOptional() { + return optional; + } + + public boolean isSelected() { + return selected; + } + + abstract int parse(String[] args, int i) throws CliParseException; + + abstract int appendSynopsis(StringBuilder sb); + + abstract void appendDescription(StringBuilder sb); + +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/CliParameterBuilder.java b/src/main/java/org/openmuc/j60870/internal/cli/CliParameterBuilder.java new file mode 100644 index 0000000..b333af1 --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/CliParameterBuilder.java @@ -0,0 +1,51 @@ +package org.openmuc.j60870.internal.cli; + +public class CliParameterBuilder { + + final String name; + String description; + boolean optional = true; + + public CliParameterBuilder(String name) { + this.name = name; + } + + public CliParameterBuilder setDescription(String description) { + this.description = description; + return this; + } + + public CliParameterBuilder setMandatory() { + optional = false; + return this; + } + + public LongCliParameter buildLongParameter(String parameterName, long defaultValue) { + return new LongCliParameter(this, parameterName, defaultValue); + } + + public LongCliParameter buildLongParameter(String parameterName) { + return new LongCliParameter(this, parameterName); + } + + public IntCliParameter buildIntegerParameter(String parameterName, int defaultValue) { + return new IntCliParameter(this, parameterName, defaultValue); + } + + public IntCliParameter buildIntParameter(String parameterName) { + return new IntCliParameter(this, parameterName); + } + + public StringCliParameter buildStringParameter(String parameterName, String defaultValue) { + return new StringCliParameter(this, parameterName, defaultValue); + } + + public StringCliParameter buildStringParameter(String parameterName) { + return new StringCliParameter(this, parameterName); + } + + public FlagCliParameter buildFlagParameter() { + return new FlagCliParameter(this); + } + +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/CliParseException.java b/src/main/java/org/openmuc/j60870/internal/cli/CliParseException.java new file mode 100644 index 0000000..715416c --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/CliParseException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011-16 Fraunhofer ISE + * + * This file is part of OpenMUC. + * For more information visit http://www.openmuc.org + * + * OpenMUC is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenMUC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMUC. If not, see . + * + */ + +package org.openmuc.j60870.internal.cli; + +public final class CliParseException extends Exception { + + private static final long serialVersionUID = -5162894897245715377L; + + public CliParseException() { + super(); + } + + public CliParseException(String s) { + super(s); + } + + public CliParseException(Throwable cause) { + super(cause); + } + + public CliParseException(String s, Throwable cause) { + super(s, cause); + } + +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/CliParser.java b/src/main/java/org/openmuc/j60870/internal/cli/CliParser.java new file mode 100644 index 0000000..787029d --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/CliParser.java @@ -0,0 +1,127 @@ +package org.openmuc.j60870.internal.cli; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class CliParser { + + private final String name; + private final String description; + private String selectedGroup = ""; + + private static class ParameterGroup { + private final String name; + private final List parameters; + + public ParameterGroup(String name, List parameters) { + this.name = name; + this.parameters = parameters; + } + } + + private final List commandLineParameterGroups = new ArrayList(); + + public CliParser(String name, String description) { + this.name = name; + this.description = description; + } + + public void addParameterGroup(String groupName, List parameters) { + commandLineParameterGroups.add(new ParameterGroup(groupName.toLowerCase(), parameters)); + } + + public void addParameters(List parameters) { + commandLineParameterGroups.clear(); + commandLineParameterGroups.add(new ParameterGroup("", parameters)); + } + + public String getSelectedGroup() { + return selectedGroup; + } + + public void parseArguments(String[] args) throws CliParseException { + + List parameters = null; + + int i = 0; + if (commandLineParameterGroups.get(0).name.isEmpty()) { + parameters = commandLineParameterGroups.get(0).parameters; + } + else { + if (args.length == 0) { + throw new CliParseException("No parameters found."); + } + for (ParameterGroup parameterGroup : commandLineParameterGroups) { + if (parameterGroup.name.equals(args[0].toLowerCase())) { + selectedGroup = parameterGroup.name; + parameters = parameterGroup.parameters; + } + } + if (parameters == null) { + throw new CliParseException("Group name " + args[0] + " is undefined."); + } + i++; + } + + while (i < args.length) { + boolean found = false; + for (CliParameter option : parameters) { + if (args[i].equals(option.getName())) { + i += option.parse(args, i); + found = true; + break; + } + } + if (!found) { + throw new CliParseException("Unknown parameter found: " + args[i]); + } + } + + for (CliParameter option : parameters) { + if (!option.isOptional() && !option.isSelected()) { + throw new CliParseException("Parameter " + option.getName() + " is mandatory but was not selected."); + } + } + } + + public String getUsageString() { + + StringBuilder sb = new StringBuilder(); + sb.append("NAME\n\t").append(name).append(" - ").append(description).append("\n\nSYNOPSIS\n"); + + for (ParameterGroup parameterGroup : commandLineParameterGroups) { + sb.append("\t").append(name).append(" ").append(parameterGroup.name); + + int characterColumn = name.length() + parameterGroup.name.length() + 1; + + for (CliParameter parameter : parameterGroup.parameters) { + if ((characterColumn + parameter.appendSynopsis(new StringBuilder())) > 90) { + characterColumn = 0; + sb.append("\n\t "); + } + sb.append(' '); + characterColumn += parameter.appendSynopsis(sb) + 1; + } + sb.append("\n"); + } + + sb.append("\nOPTIONS\n"); + + Set parameters = new LinkedHashSet(); + + for (ParameterGroup parameterGroup : commandLineParameterGroups) { + parameters.addAll(parameterGroup.parameters); + } + + for (CliParameter parameter : parameters) { + sb.append(' '); + parameter.appendDescription(sb); + sb.append("\n\n"); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/FlagCliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/FlagCliParameter.java new file mode 100644 index 0000000..f0fcccf --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/FlagCliParameter.java @@ -0,0 +1,36 @@ +package org.openmuc.j60870.internal.cli; + +public class FlagCliParameter extends CliParameter { + + FlagCliParameter(CliParameterBuilder builder) { + super(builder); + } + + @Override + int appendSynopsis(StringBuilder sb) { + int length = 0; + if (optional) { + sb.append("["); + length++; + } + sb.append(name); + length += name.length(); + if (optional) { + sb.append("]"); + length++; + } + return length; + } + + @Override + void appendDescription(StringBuilder sb) { + sb.append("\t").append(name).append("\n\t ").append(description); + } + + @Override + int parse(String[] args, int i) throws CliParseException { + selected = true; + return 1; + } + +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/IntCliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/IntCliParameter.java new file mode 100644 index 0000000..cde8a21 --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/IntCliParameter.java @@ -0,0 +1,43 @@ +package org.openmuc.j60870.internal.cli; + +public class IntCliParameter extends ValueCliParameter { + + Integer value; + + IntCliParameter(CliParameterBuilder builder, String parameterName, int defaultValue) { + super(builder, parameterName); + value = defaultValue; + } + + IntCliParameter(CliParameterBuilder builder, String parameterName) { + super(builder, parameterName); + } + + public int getValue() { + return value; + } + + @Override + int parse(String[] args, int i) throws CliParseException { + selected = true; + + if (args.length < (i + 2)) { + throw new CliParseException("Parameter " + name + " has no value."); + } + + try { + value = Integer.decode(args[i + 1]); + } catch (Exception e) { + throw new CliParseException("Parameter value " + args[i + 1] + " cannot be converted to int."); + } + return 2; + } + + @Override + void appendDescription(StringBuilder sb) { + super.appendDescription(sb); + if (value != null) { + sb.append(" Default is ").append(value).append("."); + } + } +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/LongCliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/LongCliParameter.java new file mode 100644 index 0000000..65e499c --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/LongCliParameter.java @@ -0,0 +1,43 @@ +package org.openmuc.j60870.internal.cli; + +public class LongCliParameter extends ValueCliParameter { + + Long value; + + LongCliParameter(CliParameterBuilder builder, String parameterName, long defaultValue) { + super(builder, parameterName); + value = defaultValue; + } + + LongCliParameter(CliParameterBuilder builder, String parameterName) { + super(builder, parameterName); + } + + public long getValue() { + return value; + } + + @Override + int parse(String[] args, int i) throws CliParseException { + selected = true; + + if (args.length < (i + 2)) { + throw new CliParseException("Parameter " + name + " has no value."); + } + + try { + value = Long.decode(args[i + 1]); + } catch (Exception e) { + throw new CliParseException("Parameter value " + args[i + 1] + " cannot be converted to long."); + } + return 2; + } + + @Override + void appendDescription(StringBuilder sb) { + super.appendDescription(sb); + if (value != null) { + sb.append(" Default is ").append(value).append("."); + } + } +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/StringCliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/StringCliParameter.java new file mode 100644 index 0000000..e45bf4a --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/StringCliParameter.java @@ -0,0 +1,39 @@ +package org.openmuc.j60870.internal.cli; + +public class StringCliParameter extends ValueCliParameter { + + String value; + + StringCliParameter(CliParameterBuilder builder, String parameterName, String defaultValue) { + super(builder, parameterName); + value = defaultValue; + } + + StringCliParameter(CliParameterBuilder builder, String parameterName) { + super(builder, parameterName); + } + + public String getValue() { + return value; + } + + @Override + int parse(String[] args, int i) throws CliParseException { + selected = true; + + if (args.length < (i + 2)) { + throw new CliParseException("Parameter " + name + " has no value."); + } + value = args[i + 1]; + + return 2; + } + + @Override + public void appendDescription(StringBuilder sb) { + super.appendDescription(sb); + if (value != null) { + sb.append(" Default is ").append(value).append("."); + } + } +} diff --git a/src/main/java/org/openmuc/j60870/internal/cli/ValueCliParameter.java b/src/main/java/org/openmuc/j60870/internal/cli/ValueCliParameter.java new file mode 100644 index 0000000..c544375 --- /dev/null +++ b/src/main/java/org/openmuc/j60870/internal/cli/ValueCliParameter.java @@ -0,0 +1,32 @@ +package org.openmuc.j60870.internal.cli; + +abstract class ValueCliParameter extends CliParameter { + + String parameterName; + + ValueCliParameter(CliParameterBuilder builder, String parameterName) { + super(builder); + this.parameterName = parameterName; + } + + @Override + int appendSynopsis(StringBuilder sb) { + int length = 0; + if (optional) { + sb.append("["); + length++; + } + sb.append(name).append(" <").append(parameterName).append(">"); + length += (name.length() + 3 + parameterName.length()); + if (optional) { + sb.append("]"); + length++; + } + return length; + } + + @Override + void appendDescription(StringBuilder sb) { + sb.append("\t").append(name).append(" <").append(parameterName).append(">\n\t ").append(description); + } +} diff --git a/src/sample/java/SampleServer.java b/src/sample/java/SampleServer.java deleted file mode 100644 index 9709f14..0000000 --- a/src/sample/java/SampleServer.java +++ /dev/null @@ -1,121 +0,0 @@ - -/* - * This file is part of j60870. - * For more information visit http://www.openmuc.org - * - * You are free to use code of this sample file in any - * way you like and without any restrictions. - * - */ -import java.io.EOFException; -import java.io.IOException; -import java.util.concurrent.TimeoutException; - -import org.openmuc.j60870.ASdu; -import org.openmuc.j60870.CauseOfTransmission; -import org.openmuc.j60870.Connection; -import org.openmuc.j60870.ConnectionEventListener; -import org.openmuc.j60870.IeQuality; -import org.openmuc.j60870.IeScaledValue; -import org.openmuc.j60870.InformationElement; -import org.openmuc.j60870.InformationObject; -import org.openmuc.j60870.ServerSap; -import org.openmuc.j60870.ServerSapListener; -import org.openmuc.j60870.TypeId; - -public class SampleServer implements ServerSapListener, ConnectionEventListener { - - private static int connectionIdCounter = 1; - - private Connection connection; - private int connectionId; - - public static void main(String[] args) { - ServerSap serverSap = new ServerSap(new SampleServer()); - - try { - serverSap.startListening(); - } catch (IOException e) { - System.out.println("Unable to start listening: \"" + e.getMessage() + "\". Will quit."); - } - } - - public SampleServer() { - } - - public SampleServer(Connection connection, int connectionId) { - this.connection = connection; - this.connectionId = connectionId; - } - - @Override - public void connectionIndication(Connection connection) { - - int myConnectionId = connectionIdCounter++; - System.out.println("A client has connected using TCP/IP. Will listen for a StartDT request. Connection ID: " - + myConnectionId); - - try { - connection.waitForStartDT(new SampleServer(connection, myConnectionId), 5000); - } catch (IOException e) { - System.out.println("Connection (" + myConnectionId + ") interrupted while waiting for StartDT: " - + e.getMessage() + ". Will quit."); - return; - } catch (TimeoutException e) { - } - - System.out.println( - "Started data transfer on connection (" + myConnectionId + ") Will listen for incoming commands."); - - } - - @Override - public void serverStoppedListeningIndication(IOException e) { - System.out.println("Server has stopped listening for new connections : \"" + e.getMessage() + "\". Will quit."); - } - - @Override - public void newASdu(ASdu aSdu) { - try { - - switch (aSdu.getTypeIdentification()) { - // interrogation command - case C_IC_NA_1: - connection.sendConfirmation(aSdu); - System.out.println("Got interrogation command. Will send scaled measured values.\n"); - - connection - .send(new ASdu(TypeId.M_ME_NB_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, - aSdu.getCommonAddress(), - new InformationObject[] { new InformationObject(1, - new InformationElement[][] { { new IeScaledValue(-32768), - new IeQuality(true, true, true, true, true) }, - { new IeScaledValue(10), new IeQuality(true, true, true, true, true) }, - { new IeScaledValue(-5), new IeQuality(true, true, true, true, true) } }) })); - - break; - default: - System.out.println("Got unknown request: " + aSdu + ". Will not confirm it.\n"); - } - - } catch (EOFException e) { - System.out.println( - "Will quit listening for commands on connection (" + connectionId + ") because socket was closed."); - } catch (IOException e) { - System.out.println("Will quit listening for commands on connection (" + connectionId - + ") because of error: \"" + e.getMessage() + "\"."); - } - - } - - @Override - public void connectionClosed(IOException e) { - System.out.println("Connection (" + connectionId + ") was closed. " + e.getMessage()); - } - - @Override - public void connectionAttemptFailed(IOException e) { - System.out.println("Connection attempt failed: " + e.getMessage()); - - } -} diff --git a/src/sample/java/org/openmuc/j60870/SampleServer.java b/src/sample/java/org/openmuc/j60870/SampleServer.java new file mode 100644 index 0000000..cdcbcff --- /dev/null +++ b/src/sample/java/org/openmuc/j60870/SampleServer.java @@ -0,0 +1,120 @@ +package org.openmuc.j60870; + +/* + * This file is part of j60870. + * For more information visit http://www.openmuc.org + * + * You are free to use code of this sample file in any + * way you like and without any restrictions. + * + */ +import java.io.EOFException; +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +public class SampleServer { + + public class ServerListener implements ServerEventListener { + + public class ConnectionListener implements ConnectionEventListener { + + private final Connection connection; + private final int connectionId; + + public ConnectionListener(Connection connection, int connectionId) { + this.connection = connection; + this.connectionId = connectionId; + } + + @Override + public void newASdu(ASdu aSdu) { + try { + + switch (aSdu.getTypeIdentification()) { + // interrogation command + case C_IC_NA_1: + connection.sendConfirmation(aSdu); + System.out.println("Got interrogation command. Will send scaled measured values.\n"); + + connection.send(new ASdu(TypeId.M_ME_NB_1, true, CauseOfTransmission.SPONTANEOUS, false, false, + 0, aSdu.getCommonAddress(), + new InformationObject[] { new InformationObject(1, new InformationElement[][] { + { new IeScaledValue(-32768), new IeQuality(true, true, true, true, true) }, + { new IeScaledValue(10), new IeQuality(true, true, true, true, true) }, + { new IeScaledValue(-5), new IeQuality(true, true, true, true, true) } }) })); + + break; + default: + System.out.println("Got unknown request: " + aSdu + ". Will not confirm it.\n"); + } + + } catch (EOFException e) { + System.out.println("Will quit listening for commands on connection (" + connectionId + + ") because socket was closed."); + } catch (IOException e) { + System.out.println("Will quit listening for commands on connection (" + connectionId + + ") because of error: \"" + e.getMessage() + "\"."); + } + + } + + @Override + public void connectionClosed(IOException e) { + System.out.println("Connection (" + connectionId + ") was closed. " + e.getMessage()); + } + + } + + @Override + public void connectionIndication(Connection connection) { + + int myConnectionId = connectionIdCounter++; + System.out.println("A client has connected using TCP/IP. Will listen for a StartDT request. Connection ID: " + + myConnectionId); + + try { + connection.waitForStartDT(new ConnectionListener(connection, myConnectionId), 5000); + } catch (IOException e) { + System.out.println("Connection (" + myConnectionId + ") interrupted while waiting for StartDT: " + + e.getMessage() + ". Will quit."); + return; + } catch (TimeoutException e) { + } + + System.out.println( + "Started data transfer on connection (" + myConnectionId + ") Will listen for incoming commands."); + + } + + @Override + public void serverStoppedListeningIndication(IOException e) { + System.out.println( + "Server has stopped listening for new connections : \"" + e.getMessage() + "\". Will quit."); + } + + @Override + public void connectionAttemptFailed(IOException e) { + System.out.println("Connection attempt failed: " + e.getMessage()); + + } + + } + + private int connectionIdCounter = 1; + + public static void main(String[] args) { + new SampleServer().start(); + } + + public void start() { + Server server = new Server.Builder().build(); + + try { + server.start(new ServerListener()); + } catch (IOException e) { + System.out.println("Unable to start listening: \"" + e.getMessage() + "\". Will quit."); + return; + } + } + +} diff --git a/src/test/java/org/openmuc/j60870/CP56Time2aTest.java b/src/test/java/org/openmuc/j60870/CP56Time2aTest.java index 9af9516..4315a4a 100644 --- a/src/test/java/org/openmuc/j60870/CP56Time2aTest.java +++ b/src/test/java/org/openmuc/j60870/CP56Time2aTest.java @@ -23,6 +23,12 @@ import org.junit.Assert; import org.junit.Test; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + public class CP56Time2aTest { @Test @@ -31,10 +37,10 @@ public void testTimestampToCalendar() { IeTime56 time = new IeTime56(timestamp); byte[] buffer = new byte[7]; time.encode(buffer, 0); - // Assert.assertArrayEquals(new byte[]{0x44, (byte) 0xd5, 0x1e, 0x17, 0x5d, 0x0a, 0x0d}, - // buffer); - // Assert.assertEquals(timestamp, time.getTimestamp()); + Assert.assertArrayEquals(new byte[]{0x44, (byte) 0xd5, 0x1e, 0x17, 0x5d, 0x0a, 0x0d}, + buffer); + Assert.assertEquals(timestamp, time.getTimestamp()); } } diff --git a/src/test/java/org/openmuc/j60870/test/ClientServerITest.java b/src/test/java/org/openmuc/j60870/test/ClientServerITest.java index c75df71..562d407 100644 --- a/src/test/java/org/openmuc/j60870/test/ClientServerITest.java +++ b/src/test/java/org/openmuc/j60870/test/ClientServerITest.java @@ -27,7 +27,7 @@ import org.junit.Test; import org.openmuc.j60870.ASdu; import org.openmuc.j60870.CauseOfTransmission; -import org.openmuc.j60870.ClientSap; +import org.openmuc.j60870.ClientConnectionBuilder; import org.openmuc.j60870.Connection; import org.openmuc.j60870.ConnectionEventListener; import org.openmuc.j60870.IeBinaryCounterReading; @@ -55,18 +55,17 @@ import org.openmuc.j60870.IeValueWithTransientState; import org.openmuc.j60870.InformationElement; import org.openmuc.j60870.InformationObject; -import org.openmuc.j60870.ServerSap; -import org.openmuc.j60870.ServerSapListener; +import org.openmuc.j60870.Server; +import org.openmuc.j60870.ServerEventListener; import org.openmuc.j60870.TypeId; import org.openmuc.j60870.Util; -public class ClientServerITest implements ServerSapListener, ConnectionEventListener { +public class ClientServerITest implements ServerEventListener, ConnectionEventListener { private final static int port = 2404; String host = "127.0.0.1"; - ClientSap clientSap = new ClientSap(); - ServerSap serverSap = null; + Server serverSap = null; int counter = 0; int serverCounter = 0; int counter2 = 0; @@ -179,16 +178,16 @@ else if (serverCounter == 6) { new InformationObject(1, new InformationElement[][] { { new IeSinglePointWithQuality(true, true, true, true, true) } }), - new InformationObject(2, new InformationElement[][] { - { new IeSinglePointWithQuality(false, false, false, false, false) } }) })); + new InformationObject(2, + new InformationElement[][] { { new IeSinglePointWithQuality(false, + false, false, false, false) } }) })); // 2 - serverConnection - .send(new ASdu(TypeId.M_SP_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, - aSdu.getCommonAddress(), - new InformationObject[] { new InformationObject(1, - new InformationElement[][] { { new IeSinglePointWithQuality(true, true, - true, true, true) }, + serverConnection.send(new ASdu(TypeId.M_SP_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, + false, 0, aSdu.getCommonAddress(), + new InformationObject[] { new InformationObject(1, + new InformationElement[][] { + { new IeSinglePointWithQuality(true, true, true, true, true) }, { new IeSinglePointWithQuality(false, false, false, false, false) } }) })); // 3 @@ -217,66 +216,60 @@ else if (serverCounter == 6) { // 5 serverConnection - .send(new ASdu(TypeId.M_ST_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, - 0, aSdu - .getCommonAddress(), - new InformationObject[] { new InformationObject(1, - new InformationElement[][] { - { new IeValueWithTransientState(-5, true), - new IeQuality(true, true, true, true, true) }, - { new IeValueWithTransientState(-5, false), - new IeQuality(true, true, true, true, true) }, - { new IeValueWithTransientState(-64, true), - new IeQuality(true, true, true, true, true) }, - { new IeValueWithTransientState(10, false), - new IeQuality(true, true, true, true, true) } }) })); - - // 6 - serverConnection - .send(new ASdu(TypeId.M_BO_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, + .send(new ASdu(TypeId.M_ST_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, aSdu.getCommonAddress(), new InformationObject[] { new InformationObject(1, new InformationElement[][] { - { new IeBinaryStateInformation(0xff), new IeQuality(true, - true, true, true, true) }, - { new IeBinaryStateInformation(0xffffffff), - new IeQuality(true, true, true, true, true) } }) })); + { new IeValueWithTransientState(-5, true), + new IeQuality(true, true, true, true, true) }, + { new IeValueWithTransientState(-5, false), + new IeQuality(true, true, true, true, true) }, + { new IeValueWithTransientState(-64, true), + new IeQuality(true, true, true, true, true) }, + { new IeValueWithTransientState(10, false), new IeQuality( + true, true, true, true, true) } }) })); + + // 6 + serverConnection.send(new ASdu(TypeId.M_BO_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, + false, 0, aSdu.getCommonAddress(), + new InformationObject[] { new InformationObject(1, + new InformationElement[][] { + { new IeBinaryStateInformation(0xff), + new IeQuality(true, true, true, true, true) }, + { new IeBinaryStateInformation(0xffffffff), + new IeQuality(true, true, true, true, true) } }) })); // 7 serverConnection.send(new ASdu(TypeId.M_ME_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, aSdu.getCommonAddress(), new InformationObject[] { new InformationObject(1, new InformationElement[][] { - { new IeNormalizedValue(-32768), new IeQuality(true, true, true, true, true) }, - { new IeNormalizedValue(0), new IeQuality(true, true, true, true, true) } }) })); + { new IeNormalizedValue(-32768), new IeQuality(true, true, true, true, true) }, { + new IeNormalizedValue(0), + new IeQuality(true, true, true, true, true) } }) })); // 8 - serverConnection - .send(new ASdu(TypeId.M_ME_NB_1, true, CauseOfTransmission.SPONTANEOUS, false, false, - 0, aSdu - .getCommonAddress(), - new InformationObject[] { new InformationObject(1, - new InformationElement[][] { { new IeScaledValue(-32768), - new IeQuality(true, true, true, true, true) }, - { new IeScaledValue(10), new IeQuality(true, true, true, true, true) }, - { new IeScaledValue(-5), - new IeQuality(true, true, true, true, true) } }) })); + serverConnection.send(new ASdu(TypeId.M_ME_NB_1, true, CauseOfTransmission.SPONTANEOUS, false, + false, 0, aSdu.getCommonAddress(), + new InformationObject[] { new InformationObject(1, new InformationElement[][] { + { new IeScaledValue(-32768), new IeQuality(true, true, true, true, true) }, + { new IeScaledValue(10), new IeQuality(true, true, true, true, true) }, + { new IeScaledValue(-5), new IeQuality(true, true, true, true, true) } }) })); serverConnection.send(new ASdu(TypeId.M_ME_NC_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, aSdu.getCommonAddress(), new InformationObject[] { new InformationObject(1, new InformationElement[][] { - { new IeShortFloat(-32768.2f), new IeQuality(true, true, true, true, true) }, - { new IeShortFloat(10.5f), new IeQuality(true, true, true, true, true) } }) })); + { new IeShortFloat(-32768.2f), new IeQuality(true, true, true, true, true) }, { + new IeShortFloat(10.5f), + new IeQuality(true, true, true, true, true) } }) })); // 10 - serverConnection - .send(new ASdu(TypeId.M_IT_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, false, 0, - aSdu.getCommonAddress(), - new InformationObject[] { - new InformationObject(1, - new InformationElement[][] { { new IeBinaryCounterReading(-300, 5, - true, true, true) }, - { new IeBinaryCounterReading(-300, 4, false, false, false) } }) })); + serverConnection.send(new ASdu(TypeId.M_IT_NA_1, true, CauseOfTransmission.SPONTANEOUS, false, + false, 0, aSdu.getCommonAddress(), + new InformationObject[] { new InformationObject(1, + new InformationElement[][] { + { new IeBinaryCounterReading(-300, 5, true, true, true) }, { + new IeBinaryCounterReading(-300, 4, false, false, false) } }) })); serverConnection.send(new ASdu(TypeId.M_EP_TA_1, false, CauseOfTransmission.SPONTANEOUS, false, false, 0, aSdu.getCommonAddress(), @@ -305,14 +298,13 @@ else if (serverCounter == 6) { new IeTime16(300), new IeTime24(400) } }) })); serverConnection - .send(new ASdu(TypeId.M_PS_NA_1, false, CauseOfTransmission.SPONTANEOUS, false, false, - 0, aSdu - .getCommonAddress(), + .send(new ASdu(TypeId.M_PS_NA_1, false, CauseOfTransmission.SPONTANEOUS, false, false, 0, + aSdu.getCommonAddress(), new InformationObject[] { new InformationObject(1, new InformationElement[][] { { - new IeStatusAndStatusChanges(0xff0000ff), - new IeQuality(true, true, true, true, true) } }) })); + new IeStatusAndStatusChanges(0xff0000ff), new IeQuality( + true, true, true, true, true) } }) })); Thread.sleep(1000); @@ -366,11 +358,11 @@ public void connectionClosed(IOException e) { @Test public void testClientServerCom() throws Exception { - serverSap = new ServerSap(port, this); - serverSap.startListening(); - - Connection clientConnection = clientSap.connect(InetAddress.getByName("127.0.0.1"), port); + serverSap = new Server.Builder().setPort(port).build();// (port, this); + serverSap.start(this); + Connection clientConnection = new ClientConnectionBuilder(InetAddress.getByName("127.0.0.1")).setPort(port) + .connect(); try { clientConnection.startDataTransfer(this, 5000);