You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2019/02/07 21:30:07 UTC
[incubator-plc4x] 01/02: - Continued working on the dynamic s7
driver
This is an automated email from the ASF dual-hosted git repository.
cdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
commit 66aebb38a350411a25dfa149ce996a992924905f
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Thu Feb 7 15:22:50 2019 +0100
- Continued working on the dynamic s7 driver
(In it's current state it is able to connect to a remote PLC and identify the type)
---
.../apache/plc4x/protocols/s7/protocol.dfdl.xsd | 2 +-
.../apache/plc4x/protocols/s7/protocol.scxml.xml | 248 +++++++++------------
.../org/apache/plc4x/protocols/s7/protocol.tdml | 4 +-
sandbox/dynamic-driver-s7/pom.xml | 32 +++
.../java/org/apache/plc4x/sandbox/java/s7/Poc.java | 32 ++-
.../java/s7/actions/BaseConnectedAction.java | 34 +++
...asePlc4xAction.java => BaseDaffodilAction.java} | 22 +-
.../sandbox/java/s7/actions/BasePlc4xAction.java | 58 ++---
.../sandbox/java/s7/actions/ConnectAction.java | 23 +-
.../sandbox/java/s7/actions/InitContextAction.java | 61 ++---
.../sandbox/java/s7/actions/ReceiveAction.java | 191 +++++++++++++---
.../plc4x/sandbox/java/s7/actions/SendAction.java | 25 +--
.../java/s7/actions/s7/S7DecodeArticleNumber.java | 91 ++++++++
.../s7/utils/W3CDOMTemplateInfosetInputter.scala | 42 ++++
14 files changed, 571 insertions(+), 294 deletions(-)
diff --git a/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.dfdl.xsd b/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.dfdl.xsd
index 07715ab..b0ec9f8 100644
--- a/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.dfdl.xsd
+++ b/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.dfdl.xsd
@@ -177,7 +177,7 @@
</xs:choice>
<xs:element name="userData" type="s7:S7MessageType" minOccurs="0"
dfdl:occursCountKind="expression"
- dfdl:occursCount="{if((../../length - (../headerLength + 1)) gt 0) then 1 else 0}"/>
+ dfdl:occursCount="{if((../../length - (../headerLength + 5)) gt 0) then 1 else 0}"/>
</xs:sequence>
</xs:complexType>
diff --git a/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.scxml.xml b/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.scxml.xml
index e272f77..bfab15c 100644
--- a/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.scxml.xml
+++ b/protocols/s7/src/main/resources/org/apache/plc4x/protocols/s7/protocol.scxml.xml
@@ -25,25 +25,42 @@
datamodel="jexl"
xsi:schemaLocation="http://www.w3.org/2005/07/scxml http://www.w3.org/2011/04/SCXML/scxml.xsd">
+ <!-- Define all the variables we're going to use -->
<sc:datamodel>
- <sc:data id="args"/>
+ <sc:data id="protocolDaffodilSchema"/>
+ <sc:data id="cotpLocalReference"/>
+ <sc:data id="cotpCalledTsap"/>
+ <sc:data id="cotpCallingTsap"/>
+ <sc:data id="cotpTpduSize"/>
+ <sc:data id="s7MaxAmqCaller"/>
+ <sc:data id="s7MaxAmqCallee"/>
+ <sc:data id="s7PduLength"/>
+ <sc:data id="s7ArticleNumber"/>
+ <sc:data id="plcType"/>
</sc:datamodel>
+ <!--
+ Setup the initial state ... this usually just initializes the Daffodil subsystem.
+ -->
<sc:state id="init">
<sc:onentry>
<!-- Setup the initial content of the connection context (Callback in the driver) -->
- <plc4x:initContext/>
+ <plc4x:initContext protocolDaffodilSchemaName="protocolDaffodilSchema"/>
</sc:onentry>
<sc:transition event="success" target="connect">
<sc:assign location="args" expr="_event.data"/>
</sc:transition>
</sc:state>
+ <!--
+ Sub-Statemachine handling the connection establishment.
+ -->
<sc:state id="connect">
<sc:initial>
<sc:transition target="establishNetworkConnection"/>
</sc:initial>
+ <!-- This step establishes the physical connection to the remote -->
<sc:state id="establishNetworkConnection">
<sc:onentry>
<!-- Initialize the network connection to the remote host using the tcp adapter with a given host and port -->
@@ -53,9 +70,20 @@
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ First we have to connect on COTP level, so send CTOP connection request first.
+ Some parameters we are sending are a suggestion from our side, the remote will
+ respond with values it sees more fitting.
+
+ For example the "called-tsap" we just make up an id and the remote will respond
+ with its real id.
+
+ The pdu size is the one is the maximum PDU size we can live with, the remote
+ will respond with a size that is at most this big (usually it's smaller).
+ -->
<sc:state id="sendCotpConnectionRequest">
<sc:onentry>
- <plc4x:send socketParameterName="connection">
+ <plc4x:send>
<s7:TpktMessage>
<magicByte>3</magicByte>
<reserved>0</reserved>
@@ -65,29 +93,29 @@
<type>224</type>
<s7:CotpTpduConnectionRequest>
<destinationReference>0</destinationReference>
- <!-- Insert the value for "cotp-local-reference" as short here -->
- <sourceReference>15</sourceReference><!--plc4x:insert type="s7:short" name="cotp-local-reference"/-->
+ <!-- Insert the value for "cotpLocalReference" as short here -->
+ <sourceReference>${cotpLocalReference}</sourceReference>
<protocolClass>0</protocolClass>
<s7:parameters>
<parameter>
<type>194</type>
<parameterLength>2</parameterLength>
<s7:CotpParameterCalledTsap>
- <tsapId>512</tsapId><!--plc4x:insert type="s7:short" name="cotp-called-tsap"/-->
+ <tsapId>${cotpCalledTsap}</tsapId>
</s7:CotpParameterCalledTsap>
</parameter>
<parameter>
<type>193</type>
<parameterLength>2</parameterLength>
<s7:CotpParameterCallingTsap>
- <tsapId>273</tsapId><!--plc4x:insert type="s7:short" name="cotp-calling-tsap"/-->
+ <tsapId>${cotpCallingTsap}</tsapId>
</s7:CotpParameterCallingTsap>
</parameter>
<parameter>
<type>192</type>
<parameterLength>1</parameterLength>
<s7:CotpParameterTpduSize>
- <tpduSize>10</tpduSize><!--plc4x:insert type="s7:byte" name="cotp-tpdu-size"/-->
+ <tpduSize>${cotpTpduSize}</tpduSize>
</s7:CotpParameterTpduSize>
</parameter>
</s7:parameters>
@@ -100,23 +128,33 @@
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ If everything wen't fine, the remote should respond with a connection response.
+ As mentioned before we now have to update some of the values with the ones the
+ remote responded with.
+ -->
<sc:state id="receiveCotpConnectionResponse">
<sc:onentry>
- <plc4x:receive socketParameterName="connection" timeout="5000">
- <verification name="cotp-local-reference" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/destinationReference"/>
- <extraction name="cotp-remote-reference" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/sourceReference"/>
- <extraction name="cotp-tpdu-size" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterTpduSize/tpduSize"/>
- <extraction name="cotp-calling-tsap" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterCallingTsap/tsapId"/>
- <extraction name="cotp-called-tsap" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterCalledTsap/tsapId"/>
+ <plc4x:receive timeout="5000" packetLengthStartPosition="2" packetLengthSizeInBytes="2">
+ <verification name="cotpLocalReference" xpath-expression="/s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/destinationReference/text()[1]"/>
+ <extraction name="cotpRemoteReference" xpath-expression="/s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/sourceReference/text()[1]"/>
+ <extraction name="cotpTpduSize" xpath-expression="/s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterTpduSize/tpduSize/text()[1]"/>
+ <extraction name="cotpCallingTsap" xpath-expression="/s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterCallingTsap/tsapId/text()[1]"/>
+ <extraction name="cotpCalledTsap" xpath-expression="/s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/s7:parameters/parameter/s7:CotpParameterCalledTsap/tsapId/text()[1]"/>
</plc4x:receive>
</sc:onentry>
<sc:transition event="success" target="sendS7SetupCommunicationRequest"/>
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ After the connection is established on COTP level, we now do the same on the S7
+ protocol level. Again we will be sending values we think are ok and in the
+ response the remote will tell us what it can live with.
+ -->
<sc:state id="sendS7SetupCommunicationRequest">
<sc:onentry>
- <plc4x:send socketParameterName="connection">
+ <plc4x:send>
<s7:TpktMessage>
<magicByte>3</magicByte>
<reserved>0</reserved>
@@ -136,17 +174,17 @@
<tpduReference>0</tpduReference>
<parametersLength>8</parametersLength>
<payloadsLength>0</payloadsLength>
- <s7:parameters>
+ <parameters>
<parameter>
<type>240</type>
<s7:S7GeneralParameterSetupCommunication>
<reserved>0</reserved>
- <maxAmqCaller>10</maxAmqCaller><!--plc4x:insert type="s7:short" name="s7-max-amq-caller"/-->
- <maxAmqCallee>10</maxAmqCallee><!--plc4x:insert type="s7:short" name="s7-max-amq-callee"/-->
- <pduLength>1024</pduLength><!--plc4x:insert type="s7:short" name="s7-pdu-length"/-->
+ <maxAmqCaller>${s7MaxAmqCaller}</maxAmqCaller>
+ <maxAmqCallee>${s7MaxAmqCallee}</maxAmqCallee>
+ <pduLength>${s7PduLength}</pduLength>
</s7:S7GeneralParameterSetupCommunication>
</parameter>
- </s7:parameters>
+ </parameters>
<payloads>
<payload>
<s7:S7GeneralPayloadSetupCommunication/>
@@ -162,24 +200,38 @@
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ If everything wen't fine, the remote should respond with a connection response.
+ As mentioned before we now have to update some of the values with the ones the
+ remote responded with.
+
+ If a "plcType" was provided, we are now connected.
+
+ If no "plcType" was provided, the type has to be discovered by sending another
+ request and processing that response first.
+ -->
<sc:state id="receiveS7SetupCommunicationResponse">
<sc:onentry>
- <plc4x:receive socketParameterName="connection" timeout="5000">
- <verification name="cotp-local-reference" xpath="s7:TpktMessage/userData/s7:CotpTpduConnectionResponse/destinationReference"/>
- <extraction name="returnCode" xpath="s7:TpktMessage/userData/userData/s7:S7ResponseMessage/errorCode"/>
- <extraction name="s7-max-amq-caller" xpath="s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/s7:S7GeneralParameterSetupCommunication/maxAmqCaller"/>
- <extraction name="s7-max-amq-callee" xpath="s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/s7:S7GeneralParameterSetupCommunication/maxAmqCallee"/>
- <extraction name="s7-pdu-length" xpath="s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/s7:S7GeneralParameterSetupCommunication/pduLength"/>
+ <plc4x:receive timeout="5000" packetLengthStartPosition="2" packetLengthSizeInBytes="2">
+ <extraction name="returnCode" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7ResponseMessage/errorCode/text()[1]"/>
+ <extraction name="s7MaxAmqCaller" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/parameter/s7:S7GeneralParameterSetupCommunication/maxAmqCaller/text()[1]"/>
+ <extraction name="s7MaxAmqCallee" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/parameter/s7:S7GeneralParameterSetupCommunication/maxAmqCallee/text()[1]"/>
+ <extraction name="s7PduLength" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7ResponseMessage/parameters/parameter/s7:S7GeneralParameterSetupCommunication/pduLength/text()[1]"/>
</plc4x:receive>
</sc:onentry>
- <sc:transition event="" target="sendS7IdentificationRequest"/>
- <sc:transition event="success" target="connected"/>
+ <sc:transition event="success" cond="plcType == null" target="sendS7IdentificationRequest"/>
+ <sc:transition event="success" cond="plcType != null" target="connected"/>
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ If no "plcType" was provided, an S7 identification request will ask the
+ remote to send back so-called SSLs. These contain information on the type
+ and version of the remote PLC.
+ -->
<sc:state id="sendS7IdentificationRequest">
<sc:onentry>
- <plc4x:send socketParameterName="connection">
+ <plc4x:send>
<s7:TpktMessage>
<magicByte>3</magicByte>
<reserved>0</reserved>
@@ -199,7 +251,7 @@
<tpduReference>256</tpduReference>
<parametersLength>8</parametersLength>
<payloadsLength>8</payloadsLength>
- <s7:parameters>
+ <parameters>
<parameter>
<type>0</type>
<s7:S7UserDataParameterCPUService>
@@ -212,7 +264,7 @@
<sequenceNumber>0</sequenceNumber>
</s7:S7UserDataParameterCPUService>
</parameter>
- </s7:parameters>
+ </parameters>
<payloads>
<payload>
<s7:S7UserDataPayloadCpuServices>
@@ -234,130 +286,50 @@
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ As we're currently only interested in the type of the PLC, we simply
+ take the article number returned and decode that in a S7 specific
+ custom action.
+ -->
<sc:state id="receiveS7IdentificationRequest">
<sc:onentry>
- <plc4x:receive socketParameterName="connection">
- <s7:TpktMessage>
- <magicByte>3</magicByte>
- <reserved>0</reserved>
- <length><plc4x:ignore/></length>
- <userData>
- <headerLength>2</headerLength>
- <type>240</type>
- <s7:CotpTpduData>
- <endOfTransmission>1</endOfTransmission>
- <tpduRef>0</tpduRef>
- </s7:CotpTpduData>
- <userData>
- <magicByte>50</magicByte>
- <type>7</type>
- <s7:S7UserDataMessage>
- <reserved>0</reserved>
- <tpduReference>256</tpduReference>
- <parametersLength><plc4x:ignore/></parametersLength>
- <payloadsLength><plc4x:ignore/></payloadsLength>
- <s7:parameters>
- <plc4x:unordered>
- <parameter>
- <type>0</type>
- <S7UserDataParameterCPUService>
- <header>274</header>
- <paramLength>8</paramLength>
- <typeCode>18</typeCode>
- <type>8</type>
- <functionGroup>4</functionGroup>
- <subFunctionGroup>1</subFunctionGroup>
- <sequenceNumber>2</sequenceNumber>
- <dataUnitReferenceNumber>0</dataUnitReferenceNumber>
- <lastDataUnit>0</lastDataUnit>
- <errorCode>0</errorCode>
- </S7UserDataParameterCPUService>
- </parameter>
- <plc4x:ignore/>
- </plc4x:unordered>
- </s7:parameters>
- <payloads>
- <plc4x:unordered>
- <payload>
- <S7UserDataPayloadCpuServices>
- <returnCode>255</returnCode>
- <transportSize>9</transportSize>
- <length>120</length>
- <sslId>17</sslId>
- <sslIndex>0</sslIndex>
- <partialList>
- <partialListLengthInBytes>28</partialListLengthInBytes>
- <partialListCount>4</partialListCount>
- <sslDataRecords>
- <plc4x:unordered>
- <sslDataRecord>
- <S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- <index>1</index>
- <articleNumber><plc4x:extract type="s7:short" name="s7-ssl-1"/></articleNumber>
- <bgType>192</bgType>
- <moduleOrOsVersion>3</moduleOrOsVersion>
- <pgDescriptionFileVersion>1</pgDescriptionFileVersion>
- </S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- </sslDataRecord>
- <sslDataRecord>
- <S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- <index>6</index>
- <articleNumber><plc4x:extract type="s7:short" name="s7-ssl-6"/></articleNumber>
- <bgType>192</bgType>
- <moduleOrOsVersion>3</moduleOrOsVersion>
- <pgDescriptionFileVersion>1</pgDescriptionFileVersion>
- </S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- </sslDataRecord>
- <sslDataRecord>
- <S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- <index>7</index>
- <articleNumber><plc4x:extract type="s7:short" name="s7-ssl-7"/></articleNumber>
- <bgType>192</bgType>
- <moduleOrOsVersion>22019</moduleOrOsVersion>
- <pgDescriptionFileVersion>519</pgDescriptionFileVersion>
- </S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- </sslDataRecord>
- <sslDataRecord>
- <S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- <index>129</index>
- <articleNumber><plc4x:extract type="s7:short" name="s7-ssl-129"/></articleNumber>
- <bgType>0</bgType>
- <moduleOrOsVersion>16672</moduleOrOsVersion>
- <pgDescriptionFileVersion>2313</pgDescriptionFileVersion>
- </S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification>
- </sslDataRecord>
- <plc4x:ignore/>
- </plc4x:unordered>
- </sslDataRecords>
- </partialList>
- </S7UserDataPayloadCpuServices>
- </payload>
- <plc4x:ignore/>
- </plc4x:unordered>
- </payloads>
- </s7:S7UserDataMessage>
- </userData>
- </userData>
- </s7:TpktMessage>
+ <plc4x:receive timeout="5000" packetLengthStartPosition="2" packetLengthSizeInBytes="2">
+ <!-- Ensure everything is ok -->
+ <!--verification value="255" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7UserDataMessage/payloads/payload/s7:S7UserDataPayloadCpuServices/returnCode/text()"/-->
+ <!-- Extract the article-number of the SslDataRecordModuleIdentification with the index 1 -->
+ <extraction name="s7ArticleNumber" xpath-expression="/s7:TpktMessage/userData/userData/s7:S7UserDataMessage/payloads/payload/s7:S7UserDataPayloadCpuServices/partialList/sslDataRecords/sslDataRecord//s7:S7ResponsePayloadCpuServicesSslDataRecordModuleIdentification[index='1']/articleNumber/text()"/>
</plc4x:receive>
</sc:onentry>
- <sc:transition event="success" target="connected"/>
+ <sc:transition event="success" target="connected">
+ <plc4x:S7DecodeArticleNumber articleNumberParameterName="s7ArticleNumber" plcTypeParameterName="plcType"/>
+ </sc:transition>
<sc:transition event="failure" target="error"/>
</sc:state>
+ <!--
+ Default state after connecting to a PLC.
+ -->
<sc:state id="connected">
<sc:onentry>
- <sc:log expr="'Connected'"/>
+ <sc:log expr="'Connected to PLC of type: ' + plcType"/>
</sc:onentry>
</sc:state>
+ <!--
+ Final state of this state-machine, after any of the parties disconnected.
+ -->
<sc:final id="disconnected">
-
+ <sc:onentry>
+ <sc:log expr="'Disconnected'"/>
+ </sc:onentry>
</sc:final>
+ <!--
+ Error state in case of any form of error during the processing of data.
+ -->
<sc:final id="error">
<sc:onentry>
- <sc:log expr="'Error connecting'"/>
+ <sc:log expr="'Error'"/>
</sc:onentry>
</sc:final>
</sc:state>
diff --git a/protocols/s7/src/test/resources/org/apache/plc4x/protocols/s7/protocol.tdml b/protocols/s7/src/test/resources/org/apache/plc4x/protocols/s7/protocol.tdml
index e9aff9f..c418628 100644
--- a/protocols/s7/src/test/resources/org/apache/plc4x/protocols/s7/protocol.tdml
+++ b/protocols/s7/src/test/resources/org/apache/plc4x/protocols/s7/protocol.tdml
@@ -49,7 +49,7 @@
description="Simple TKPT packet which contains a COTP Connection-Response as payload.">
<!-- Define the input -->
<tdml:document>
- <tdml:documentPart type="byte">0300001211D00001000200C00109C1020100C2020102</tdml:documentPart>
+ <tdml:documentPart type="byte">0300001611D00001000200C00109C1020100C2020102</tdml:documentPart>
</tdml:document>
<!-- Define the expected output -->
@@ -59,7 +59,7 @@
<test:tpktMessage>
<magicByte>3</magicByte>
<reserved>0</reserved>
- <length>18</length>
+ <length>22</length>
<userData>
<headerLength>17</headerLength>
<type>208</type>
diff --git a/sandbox/dynamic-driver-s7/pom.xml b/sandbox/dynamic-driver-s7/pom.xml
index 9e99f0a..f302333 100644
--- a/sandbox/dynamic-driver-s7/pom.xml
+++ b/sandbox/dynamic-driver-s7/pom.xml
@@ -58,6 +58,11 @@
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
+ <dependency>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ <version>1.1.6</version>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -74,6 +79,33 @@
<build>
<plugins>
<plugin>
+ <groupId>net.alchim31.maven</groupId>
+ <artifactId>scala-maven-plugin</artifactId>
+ <version>3.4.6</version>
+ <executions>
+ <execution>
+ <id>add-scala-sources</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sourceDir>src/main/scala</sourceDir>
+ </configuration>
+ </execution>
+ <execution>
+ <id>compile-scala</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <outputDir>${project.build.outputDirectory}</outputDir>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/Poc.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/Poc.java
index 790f2ef..04faee4 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/Poc.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/Poc.java
@@ -30,15 +30,22 @@ import org.apache.plc4x.sandbox.java.s7.actions.ConnectAction;
import org.apache.plc4x.sandbox.java.s7.actions.InitContextAction;
import org.apache.plc4x.sandbox.java.s7.actions.ReceiveAction;
import org.apache.plc4x.sandbox.java.s7.actions.SendAction;
+import org.apache.plc4x.sandbox.java.s7.actions.s7.S7DecodeArticleNumber;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
public class Poc {
+ private String dataFormatURI;
+
private SCXMLExecutor executor;
- private Poc() throws Exception {
+ private Poc(String stateMachineURI, String dataFormatURI) throws Exception {
+ this.dataFormatURI = dataFormatURI;
+
// Initialize our PLC4X specific actions.
List<CustomAction> customActions = new LinkedList<>();
customActions.add(
@@ -49,10 +56,12 @@ public class Poc {
new CustomAction("https://plc4x.apache.org/scxml-extension", "send", SendAction.class));
customActions.add(
new CustomAction("https://plc4x.apache.org/scxml-extension", "receive", ReceiveAction.class));
+ customActions.add(
+ new CustomAction("https://plc4x.apache.org/scxml-extension", "S7DecodeArticleNumber", S7DecodeArticleNumber.class));
// Initialize the state-machine with the definition from the protocol module.
SCXML scxml = SCXMLReader.read(
- Poc.class.getClassLoader().getResource("org/apache/plc4x/protocols/s7/protocol.scxml.xml"),
+ Poc.class.getClassLoader().getResource(stateMachineURI),
new SCXMLReader.Configuration(null, null, customActions));
// Create an executor for running the state-machine.
@@ -62,12 +71,27 @@ public class Poc {
}
private void run() throws Exception {
+ Map<String, Object> context = new HashMap<>();
+ context.put("protocolDaffodilSchema", dataFormatURI);
+
+ context.put("cotpLocalReference", "15");
+ context.put("cotpCalledTsap", "512");
+ context.put("cotpCallingTsap", "273");
+ context.put("cotpTpduSize", "10");
+ context.put("s7MaxAmqCaller", "10");
+ context.put("s7MaxAmqCallee", "10");
+ context.put("s7PduLength", "1024");
+
+ //context.put("plcType", "HURZ");
+
// Run the state-machine.
- executor.go();
+ executor.go(context);
}
public static void main(String[] args) throws Exception {
- Poc poc = new Poc();
+ Poc poc = new Poc(
+ "org/apache/plc4x/protocols/s7/protocol.scxml.xml",
+ "org/apache/plc4x/protocols/s7/protocol.dfdl.xsd");
poc.run();
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseConnectedAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseConnectedAction.java
new file mode 100644
index 0000000..cf37c81
--- /dev/null
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseConnectedAction.java
@@ -0,0 +1,34 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+package org.apache.plc4x.sandbox.java.s7.actions;
+
+import org.apache.commons.scxml2.ActionExecutionContext;
+
+import java.net.Socket;
+
+public abstract class BaseConnectedAction extends BasePlc4xAction {
+
+ public static final String SOCKET_PARAMETER_NAME="connection";
+
+ protected Socket getSocket(ActionExecutionContext ctx) {
+ return (Socket) ctx.getGlobalContext().get(SOCKET_PARAMETER_NAME);
+ }
+
+}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseDaffodilAction.java
similarity index 74%
copy from sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java
copy to sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseDaffodilAction.java
index e7bb675..4ed74ee 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BaseDaffodilAction.java
@@ -20,32 +20,18 @@
package org.apache.plc4x.sandbox.java.s7.actions;
import org.apache.commons.scxml2.ActionExecutionContext;
-import org.apache.commons.scxml2.model.Action;
import org.apache.commons.scxml2.model.ParsedValue;
import org.apache.commons.scxml2.model.ParsedValueContainer;
import org.apache.daffodil.japi.DataProcessor;
import org.apache.daffodil.japi.Diagnostic;
import org.apache.daffodil.japi.WithDiagnostics;
-import org.slf4j.Logger;
-import java.net.Socket;
import java.util.List;
-public abstract class BasePlc4xAction extends Action implements ParsedValueContainer {
+public abstract class BaseDaffodilAction extends BaseConnectedAction implements ParsedValueContainer {
-
-
- private String socketParameterName;
private ParsedValue message;
- public String getSocketParameterName() {
- return socketParameterName;
- }
-
- public void setSocketParameterName(String socketParameterName) {
- this.socketParameterName = socketParameterName;
- }
-
@Override
public ParsedValue getParsedValue() {
return message;
@@ -56,12 +42,6 @@ public abstract class BasePlc4xAction extends Action implements ParsedValueConta
this.message = parsedValue;
}
- protected abstract Logger getLogger();
-
- protected Socket getSocket(ActionExecutionContext ctx) {
- return (Socket) ctx.getGlobalContext().get(getSocketParameterName());
- }
-
protected DataProcessor getDaffodilDataProcessor(ActionExecutionContext ctx) {
return (DataProcessor) ctx.getGlobalContext().get("dfdl");
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java
index e7bb675..6a763b6 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/BasePlc4xAction.java
@@ -20,57 +20,33 @@
package org.apache.plc4x.sandbox.java.s7.actions;
import org.apache.commons.scxml2.ActionExecutionContext;
+import org.apache.commons.scxml2.EventBuilder;
+import org.apache.commons.scxml2.TriggerEvent;
import org.apache.commons.scxml2.model.Action;
-import org.apache.commons.scxml2.model.ParsedValue;
-import org.apache.commons.scxml2.model.ParsedValueContainer;
-import org.apache.daffodil.japi.DataProcessor;
-import org.apache.daffodil.japi.Diagnostic;
-import org.apache.daffodil.japi.WithDiagnostics;
import org.slf4j.Logger;
-import java.net.Socket;
-import java.util.List;
-
-public abstract class BasePlc4xAction extends Action implements ParsedValueContainer {
-
-
-
- private String socketParameterName;
- private ParsedValue message;
-
- public String getSocketParameterName() {
- return socketParameterName;
- }
-
- public void setSocketParameterName(String socketParameterName) {
- this.socketParameterName = socketParameterName;
- }
-
- @Override
- public ParsedValue getParsedValue() {
- return message;
- }
-
- @Override
- public void setParsedValue(ParsedValue parsedValue) {
- this.message = parsedValue;
- }
+public abstract class BasePlc4xAction extends Action {
protected abstract Logger getLogger();
- protected Socket getSocket(ActionExecutionContext ctx) {
- return (Socket) ctx.getGlobalContext().get(getSocketParameterName());
+ protected String getStateName() {
+ try {
+ return getParentEnterableState().getId();
+ } catch (Exception e) {
+ getLogger().error("Unable to get state.");
+ }
+ return "unknown";
}
- protected DataProcessor getDaffodilDataProcessor(ActionExecutionContext ctx) {
- return (DataProcessor) ctx.getGlobalContext().get("dfdl");
+ protected void fireFailureEvent(ActionExecutionContext ctx, String message) {
+ TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).
+ data(getStateName() + ": " + message).build();
+ ctx.getInternalIOProcessor().addEvent(event);
}
- protected void logDiagnosticInformation(WithDiagnostics withDiagnostics) {
- List<Diagnostic> diags = withDiagnostics.getDiagnostics();
- for (Diagnostic d : diags) {
- getLogger().error(d.getSomeMessage());
- }
+ protected void fireSuccessEvent(ActionExecutionContext ctx) {
+ TriggerEvent event = new EventBuilder("success", TriggerEvent.SIGNAL_EVENT).data(getStateName()).build();
+ ctx.getInternalIOProcessor().addEvent(event);
}
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ConnectAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ConnectAction.java
index 5a7a371..bf374d1 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ConnectAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ConnectAction.java
@@ -20,14 +20,13 @@
package org.apache.plc4x.sandbox.java.s7.actions;
import org.apache.commons.scxml2.ActionExecutionContext;
-import org.apache.commons.scxml2.EventBuilder;
-import org.apache.commons.scxml2.TriggerEvent;
-import org.apache.commons.scxml2.model.Action;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
-public class ConnectAction extends Action {
+public class ConnectAction extends BasePlc4xAction {
private String type;
private String host;
@@ -58,18 +57,24 @@ public class ConnectAction extends Action {
}
@Override
+ protected Logger getLogger() {
+ return LoggerFactory.getLogger(ConnectAction.class);
+ }
+
+ @Override
public void execute(ActionExecutionContext ctx) {
- ctx.getAppLog().info("Connecting...");
+ ctx.getAppLog().info(getStateName() + ": Connecting...");
try {
if ("TCP".equalsIgnoreCase(type)) {
Socket socket = new Socket(host, Integer.parseInt(port));
- ctx.getGlobalContext().set("connection", socket);
- TriggerEvent event = new EventBuilder("success", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
+ ctx.getGlobalContext().set(BaseConnectedAction.SOCKET_PARAMETER_NAME, socket);
+
ctx.getAppLog().info("Connected.");
+
+ fireSuccessEvent(ctx);
}
} catch (IOException e) {
- e.printStackTrace();
+ getLogger().error("Error connecting to remote.", e);
}
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/InitContextAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/InitContextAction.java
index 6b6e5f4..ed93053 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/InitContextAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/InitContextAction.java
@@ -22,62 +22,67 @@ package org.apache.plc4x.sandbox.java.s7.actions;
import org.apache.commons.scxml2.ActionExecutionContext;
import org.apache.commons.scxml2.EventBuilder;
import org.apache.commons.scxml2.TriggerEvent;
-import org.apache.commons.scxml2.model.Action;
import org.apache.daffodil.japi.Compiler;
import org.apache.daffodil.japi.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.net.URI;
-import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
-public class InitContextAction extends Action {
+public class InitContextAction extends BasePlc4xAction {
- private static final Logger logger = LoggerFactory.getLogger(InitContextAction.class);
+ private String protocolDaffodilSchemaName;
+
+ public String getProtocolDaffodilSchemaName() {
+ return protocolDaffodilSchemaName;
+ }
+
+ public void setProtocolDaffodilSchemaName(String protocolDaffodilSchemaName) {
+ this.protocolDaffodilSchemaName = protocolDaffodilSchemaName;
+ }
+
+ @Override
+ protected Logger getLogger() {
+ return LoggerFactory.getLogger(InitContextAction.class);
+ }
@Override
public void execute(ActionExecutionContext ctx) {
- ctx.getAppLog().info("Initializing Context.");
+ ctx.getAppLog().info(getStateName() + ": Initializing Context...");
try {
Compiler c = Daffodil.compiler();
c.setValidateDFDLSchemas(true);
- URL shemaUrl = SendAction.class.getClassLoader().getResource("org/apache/plc4x/protocols/s7/protocol.dfdl.xsd");
- if (shemaUrl != null) {
- URI schemaUri = shemaUrl.toURI();
+ String schemaUrlString = (String) ctx.getGlobalContext().get(protocolDaffodilSchemaName);
+ URL schemaUrl = SendAction.class.getClassLoader().getResource(schemaUrlString);
+ if (schemaUrl != null) {
+ URI schemaUri = schemaUrl.toURI();
ProcessorFactory pf = c.compileSource(schemaUri);
- if (pf.isError()) {
- logDiagnosticInformation(pf);
- TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
- return;
- }
+ logDiagnosticInformation(pf);
DataProcessor dp = pf.onPath("/");
- if (dp.isError()) {
- logDiagnosticInformation(dp);
- TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
- return;
- }
+ logDiagnosticInformation(dp);
ctx.getGlobalContext().set("dfdl", dp);
}
- } catch (IOException | URISyntaxException e) {
+ } catch (Exception e) {
+ fireFailureEvent(ctx, "Error initializing daffodil schema");
TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).data(e).build();
ctx.getInternalIOProcessor().addEvent(event);
return;
}
- TriggerEvent event = new EventBuilder("success", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
+ ctx.getAppLog().info("Context initialized.");
+ fireSuccessEvent(ctx);
}
- private void logDiagnosticInformation(WithDiagnostics withDiagnostics) {
- List<Diagnostic> diags = withDiagnostics.getDiagnostics();
- for (Diagnostic d : diags) {
- logger.error(d.getSomeMessage());
+ private void logDiagnosticInformation(WithDiagnostics withDiagnostics) throws Exception {
+ if(withDiagnostics.isError()) {
+ List<Diagnostic> diags = withDiagnostics.getDiagnostics();
+ for (Diagnostic d : diags) {
+ getLogger().error(d.getSomeMessage());
+ }
+ throw new Exception();
}
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ReceiveAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ReceiveAction.java
index d17d937..39ab99f 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ReceiveAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/ReceiveAction.java
@@ -27,27 +27,36 @@ import org.apache.commons.scxml2.model.NodeValue;
import org.apache.commons.scxml2.model.ParsedValue;
import org.apache.daffodil.japi.DataProcessor;
import org.apache.daffodil.japi.ParseResult;
-import org.apache.daffodil.japi.infoset.W3CDOMInfosetOutputter;
+import org.apache.daffodil.japi.infoset.JDOMInfosetOutputter;
+import org.apache.daffodil.japi.io.InputSourceDataInputStream;
+import org.jdom2.Document;
+import org.jdom2.Namespace;
+import org.jdom2.Text;
+import org.jdom2.filter.Filters;
+import org.jdom2.output.XMLOutputter;
+import org.jdom2.xpath.XPathExpression;
+import org.jdom2.xpath.XPathFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
-import java.io.BufferedReader;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.net.Socket;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
-public class ReceiveAction extends BasePlc4xAction {
+public class ReceiveAction extends BaseDaffodilAction {
- private String timeout;
+ private long timeout = 5000;
+ private int packetLengthStartPosition;
+ private int packetLengthSizeInBytes;
+ private int packetLengthOffset = 0;
private final Map<String, String> verificationRules;
private final Map<String, String> extractionRules;
@@ -62,19 +71,42 @@ public class ReceiveAction extends BasePlc4xAction {
return LoggerFactory.getLogger(ReceiveAction.class);
}
+ public String getPacketLengthStartPosition() {
+ return Integer.toString(packetLengthStartPosition);
+ }
+
+ public void setPacketLengthStartPosition(String packetLengthStartPosition) {
+ this.packetLengthStartPosition = Integer.valueOf(packetLengthStartPosition);
+ }
+
+ public String getPacketLengthSizeInBytes() {
+ return Integer.toString(packetLengthSizeInBytes);
+ }
+
+ public void setPacketLengthSizeInBytes(String packetLengthSizeInBytes) {
+ this.packetLengthSizeInBytes = Integer.valueOf(packetLengthSizeInBytes);
+ }
+
+ public String getPacketLengthOffset() {
+ return Integer.toString(packetLengthOffset);
+ }
+
+ public void setPacketLengthOffset(String packetLengthOffset) {
+ this.packetLengthOffset = Integer.valueOf(packetLengthOffset);
+ }
+
public String getTimeout() {
- return timeout;
+ return Long.toString(timeout);
}
public void setTimeout(String timeout) {
- this.timeout = timeout;
+ this.timeout = Long.valueOf(timeout);
}
@Override
@SuppressWarnings("unchecked")
public void setParsedValue(ParsedValue parsedValue) {
super.setParsedValue(parsedValue);
-
if(parsedValue != null) {
if(parsedValue instanceof NodeListValue) {
List<Node> ruleList = (List<Node>) parsedValue.getValue();
@@ -91,11 +123,11 @@ public class ReceiveAction extends BasePlc4xAction {
private void parseElement(Element ruleElement) {
String name = ruleElement.getAttribute("name");
- String xpath = ruleElement.getAttribute("xpath");
- if("verification".equals(ruleElement.getTagName())) {
- verificationRules.put(name, xpath);
- } else if("extraction".equals(ruleElement.getTagName())) {
- extractionRules.put(name, xpath);
+ String expression = ruleElement.getAttribute("xpath-expression");
+ if ("verification".equals(ruleElement.getTagName())) {
+ verificationRules.put(name, expression);
+ } else if ("extraction".equals(ruleElement.getTagName())) {
+ extractionRules.put(name, expression);
} else {
getLogger().error("unsupported rule type: " + ruleElement.getTagName());
}
@@ -103,43 +135,132 @@ public class ReceiveAction extends BasePlc4xAction {
@Override
public void execute(ActionExecutionContext ctx) {
- ctx.getAppLog().info("Receiving.");
+ ctx.getAppLog().info(getStateName() + ": Receiving...");
+
try {
DataProcessor dp = getDaffodilDataProcessor(ctx);
if(dp == null) {
- TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).
- data("Couldn't initialize daffodil data processor.").build();
- ctx.getInternalIOProcessor().addEvent(event);
+ fireFailureEvent(ctx, "Couldn't initialize daffodil data processor.");
return;
}
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
Socket connection = getSocket(ctx);
- BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
- System.out.println(in.readLine());
+ DataInputStream inputStream = new DataInputStream(new BufferedInputStream(connection.getInputStream()));
+
+ // Remember when we started to receive.
+ long startTime = System.currentTimeMillis();
+
+ // Check if enough bytes are available to at least find out how big the full packet is.
+ while(inputStream.available() < packetLengthStartPosition + packetLengthSizeInBytes) {
+ waitWithTimeout(ctx, startTime, timeout);
+ }
+
+ // Read these length bytes and reset the input stream back to the start.
+ inputStream.mark(packetLengthStartPosition + packetLengthSizeInBytes);
+ // Jump to the start of the length data.
+ inputStream.skip(packetLengthStartPosition);
+
+ // Read the packet length.
+ int packetLength;
+ switch (packetLengthSizeInBytes) {
+ case 1:
+ packetLength = inputStream.readUnsignedByte();
+ break;
+ case 2:
+ packetLength = inputStream.readUnsignedShort();
+ break;
+ default:
+ fireFailureEvent(ctx, "Unsupported size for packet length: " + packetLengthSizeInBytes);
+ return;
+ }
+ packetLength += packetLengthOffset;
+
+ // Go back to the beginning of the packet.
+ inputStream.reset();
- DataInputStream inputStream = new DataInputStream(connection.getInputStream());
- ReadableByteChannel rbc = Channels.newChannel(inputStream);
- W3CDOMInfosetOutputter outputter = new W3CDOMInfosetOutputter();
- ParseResult byteMessage = dp.parse(rbc, outputter);
+ // Eventually wait till the entire packet is available.
+ while(inputStream.available() < packetLength) {
+ waitWithTimeout(ctx, startTime, timeout);
+ }
+
+ byte[] packet = new byte[packetLength];
+ if(inputStream.read(packet) != packetLength) {
+ TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).
+ data("Couldn't read entire packet.").build();
+ ctx.getInternalIOProcessor().addEvent(event);
+ return;
+ }
+
+ // After having enough bytes available, process the current package.
+ JDOMInfosetOutputter outputter = new JDOMInfosetOutputter();
+ ParseResult byteMessage = dp.parse(
+ new InputSourceDataInputStream(new ByteArrayInputStream(packet)), outputter);
if (byteMessage.isError()) {
logDiagnosticInformation(byteMessage);
return;
}
+ // Get the resulting XML document from the parser.
Document message = outputter.getResult();
- System.out.println(message);
- ctx.getAppLog().info("Successfully sent message.");
+
+ // First verify all verification conditions.
+ for (Map.Entry<String, String> rule : verificationRules.entrySet()) {
+ Object reference = ctx.getGlobalContext().get(rule.getKey());
+ String current = getRuleText(message, rule.getValue());
+ if(current == null) {
+ fireFailureEvent(ctx, "Error verifying. Expected: " + reference.toString() + " got null value");
+ return;
+ }
+ if(!current.equals(reference)) {
+ fireFailureEvent(ctx, "Error verifying. Expected: " + reference.toString() + " got: " + current);
+ return;
+ }
+ }
+
+ // Then extract data from the document.
+ for (Map.Entry<String, String> rule : extractionRules.entrySet()) {
+ String current = getRuleText(message, rule.getValue());
+ if(current == null) {
+ fireFailureEvent(ctx, "Error extracting. Got null value");
+ return;
+ }
+ ctx.getGlobalContext().set(rule.getKey(), current);
+ }
} catch (IOException e) {
e.printStackTrace();
}
- TriggerEvent event = new EventBuilder("success", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
+ ctx.getAppLog().info("Received.");
+ fireSuccessEvent(ctx);
+ }
+
+ private String getRuleText(Document message, String xpathExpression) {
+ // Get the namespace definitions from the input document.
+ List<Namespace> namespaces = message.getRootElement().getNamespacesInScope();
+
+ XPathFactory xPathFactory = XPathFactory.instance();
+ XPathExpression<org.jdom2.Text> xpath = xPathFactory.compile(
+ xpathExpression, Filters.textOnly(), null, namespaces);
+ List<Text> result = xpath.evaluate(message);
+ if((result == null) || result.isEmpty()) {
+ getLogger().info("Couldn't find value for xpath expression: " + xpathExpression + " in document.");
+ if(getLogger().isInfoEnabled()) {
+ getLogger().info(new XMLOutputter().outputString(message));
+ }
+ return null;
+ }
+ return result.get(0).getTextNormalize();
+ }
+
+ private void waitWithTimeout(ActionExecutionContext ctx, long startTime, long timeout) {
+ try {
+ TimeUnit.MILLISECONDS.sleep(20);
+ if(System.currentTimeMillis() - startTime > timeout) {
+ fireFailureEvent(ctx, "Receive timed out.");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
}
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/SendAction.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/SendAction.java
index f761ac3..bd4a467 100644
--- a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/SendAction.java
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/SendAction.java
@@ -20,13 +20,11 @@
package org.apache.plc4x.sandbox.java.s7.actions;
import org.apache.commons.scxml2.ActionExecutionContext;
-import org.apache.commons.scxml2.EventBuilder;
-import org.apache.commons.scxml2.TriggerEvent;
import org.apache.commons.scxml2.model.ParsedValue;
import org.apache.daffodil.japi.DataProcessor;
import org.apache.daffodil.japi.UnparseResult;
import org.apache.daffodil.japi.infoset.InfosetInputter;
-import org.apache.daffodil.japi.infoset.W3CDOMInfosetInputter;
+import org.apache.plc4x.sandbox.java.s7.utils.W3CDOMTemplateInfosetInputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -41,7 +39,7 @@ import java.net.Socket;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
-public class SendAction extends BasePlc4xAction {
+public class SendAction extends BaseDaffodilAction {
@Override
protected Logger getLogger() {
@@ -50,7 +48,8 @@ public class SendAction extends BasePlc4xAction {
@Override
public void execute(ActionExecutionContext ctx) {
- ctx.getAppLog().info("Sending.");
+ ctx.getAppLog().info(getStateName() + ": Sending...");
+
if(getParsedValue() != null) {
if(getParsedValue().getType() == ParsedValue.ValueType.NODE) {
try {
@@ -63,12 +62,10 @@ public class SendAction extends BasePlc4xAction {
DataProcessor dp = getDaffodilDataProcessor(ctx);
if(dp == null) {
- TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).
- data("Couldn't initialize daffodil data processor.").build();
- ctx.getInternalIOProcessor().addEvent(event);
+ fireFailureEvent(ctx, "Couldn't initialize daffodil data processor.");
return;
}
- InfosetInputter inputter = new W3CDOMInfosetInputter(doc);
+ InfosetInputter inputter = new W3CDOMTemplateInfosetInputter(doc, ctx.getGlobalContext());
Socket connection = getSocket(ctx);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
@@ -79,19 +76,17 @@ public class SendAction extends BasePlc4xAction {
return;
}
outputStream.flush();
- ctx.getAppLog().info("Successfully sent message.");
} catch(IOException | ParserConfigurationException e) {
e.printStackTrace();
}
} else {
- TriggerEvent event = new EventBuilder("failure", TriggerEvent.SIGNAL_EVENT).
- data("type '" + getParsedValue().getType() + "' not supported").build();
- ctx.getInternalIOProcessor().addEvent(event);
+ fireFailureEvent(ctx, "type '" + getParsedValue().getType() + "' not supported");
return;
}
}
- TriggerEvent event = new EventBuilder("success", TriggerEvent.SIGNAL_EVENT).build();
- ctx.getInternalIOProcessor().addEvent(event);
+
+ ctx.getAppLog().info("Sent.");
+ fireSuccessEvent(ctx);
}
}
diff --git a/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/s7/S7DecodeArticleNumber.java b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/s7/S7DecodeArticleNumber.java
new file mode 100644
index 0000000..46e897b
--- /dev/null
+++ b/sandbox/dynamic-driver-s7/src/main/java/org/apache/plc4x/sandbox/java/s7/actions/s7/S7DecodeArticleNumber.java
@@ -0,0 +1,91 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+package org.apache.plc4x.sandbox.java.s7.actions.s7;
+
+import org.apache.commons.scxml2.ActionExecutionContext;
+import org.apache.commons.scxml2.model.ActionExecutionError;
+import org.apache.plc4x.sandbox.java.s7.actions.BasePlc4xAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class S7DecodeArticleNumber extends BasePlc4xAction {
+
+ private String articleNumberParameterName;
+ private String plcTypeParameterName;
+
+ public String getArticleNumberParameterName() {
+ return articleNumberParameterName;
+ }
+
+ public void setArticleNumberParameterName(String articleNumberParameterName) {
+ this.articleNumberParameterName = articleNumberParameterName;
+ }
+
+ public String getPlcTypeParameterName() {
+ return plcTypeParameterName;
+ }
+
+ public void setPlcTypeParameterName(String plcTypeParameterName) {
+ this.plcTypeParameterName = plcTypeParameterName;
+ }
+
+ @Override
+ protected Logger getLogger() {
+ return LoggerFactory.getLogger(S7DecodeArticleNumber.class);
+ }
+
+ @Override
+ public void execute(ActionExecutionContext ctx) throws ActionExecutionError {
+ String articleNumber = ctx.getGlobalContext().get(articleNumberParameterName).toString();
+ if(articleNumber == null) {
+ fireFailureEvent(ctx, "Couldn't find article number.");
+ return;
+ }
+
+ String plcType = lookupControllerType(articleNumber);
+ if(plcType == null) {
+ fireFailureEvent(ctx, "Unknown PLC type for article number: " + articleNumber);
+ }
+
+ ctx.getGlobalContext().set(plcTypeParameterName, plcType);
+ fireSuccessEvent(ctx);
+ }
+
+ private String lookupControllerType(String articleNumber) {
+ if(!articleNumber.startsWith("6ES7 ")) {
+ return null;
+ }
+
+ String model = articleNumber.substring(articleNumber.indexOf(' ') + 1, articleNumber.indexOf(' ') + 2);
+ switch (model) {
+ case "2":
+ return "S7-1200";
+ case "5":
+ return "S7-1500";
+ case "3":
+ return "S7-300";
+ case "4":
+ return "S7-400";
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/sandbox/dynamic-driver-s7/src/main/scala/org/apache/plc4x/sandbox/java/s7/utils/W3CDOMTemplateInfosetInputter.scala b/sandbox/dynamic-driver-s7/src/main/scala/org/apache/plc4x/sandbox/java/s7/utils/W3CDOMTemplateInfosetInputter.scala
new file mode 100644
index 0000000..04a83fd
--- /dev/null
+++ b/sandbox/dynamic-driver-s7/src/main/scala/org/apache/plc4x/sandbox/java/s7/utils/W3CDOMTemplateInfosetInputter.scala
@@ -0,0 +1,42 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+package org.apache.plc4x.sandbox.java.s7.utils
+
+import org.apache.commons.scxml2.Context
+import org.apache.daffodil.dpath.NodeInfo
+import org.apache.daffodil.japi.infoset.{InfosetInputterProxy, W3CDOMInfosetInputter}
+
+class W3CDOMTemplateInfosetInputter(document: org.w3c.dom.Document, context: Context) extends InfosetInputterProxy {
+
+ override val infosetInputter = new W3CDOMInfosetInputter(document)
+
+ override def getSimpleText(primType: NodeInfo.Kind): String = {
+ val value = super.getSimpleText(primType)
+ if(value.startsWith("${") && value.endsWith("}")) {
+ val varName = value.substring(2, value.length - 1)
+ val varValue = context.get(varName)
+ if(varValue.isInstanceOf[String]) {
+ return varValue.toString
+ }
+ }
+ value
+ }
+
+}