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 2020/01/27 13:00:56 UTC
[plc4x] branch develop updated: - Continued working on the
BACnet/IP driver (Enrichment of datastreams with data from EDE files)
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/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new f689ba8 - Continued working on the BACnet/IP driver (Enrichment of datastreams with data from EDE files)
f689ba8 is described below
commit f689ba87ab0a39807751d42e769b9d89a02c0dc5
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Mon Jan 27 14:00:47 2020 +0100
- Continued working on the BACnet/IP driver (Enrichment of datastreams with data from EDE files)
---
.../org/apache/plc4x/java/api/value/PlcValues.java | 3 +
pom.xml | 5 +
sandbox/test-java-bacnetip-driver/pom.xml | 4 +
.../apache/plc4x/java/bacnetip/ede/EdeParser.java | 76 +++++++++++-
.../plc4x/java/bacnetip/ede/model/Datapoint.java | 135 +++++++++++++++++++++
.../plc4x/java/bacnetip/ede/model/EdeModel.java | 15 +++
.../plc4x/java/bacnetip/field/BacNetIpField.java | 30 +++++
.../protocol/PassiveBacNetIpProtocolLogic.java | 54 +++++----
.../java/bacnetip/PassiveBacNetIpDriverManual.java | 10 +-
9 files changed, 307 insertions(+), 25 deletions(-)
diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java
index 244d784..e008c87 100644
--- a/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/value/PlcValues.java
@@ -356,6 +356,9 @@ public class PlcValues {
}
public static PlcValue of(Object o) {
+ if(o == null) {
+ return null;
+ }
try {
String simpleName = o.getClass().getSimpleName();
Class<?> clazz = o.getClass();
diff --git a/pom.xml b/pom.xml
index 07826aa..47e8772 100644
--- a/pom.xml
+++ b/pom.xml
@@ -289,6 +289,11 @@
<version>1.19</version>
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-csv</artifactId>
+ <version>1.7</version>
+ </dependency>
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
diff --git a/sandbox/test-java-bacnetip-driver/pom.xml b/sandbox/test-java-bacnetip-driver/pom.xml
index f0cf3f2..46b79aa 100644
--- a/sandbox/test-java-bacnetip-driver/pom.xml
+++ b/sandbox/test-java-bacnetip-driver/pom.xml
@@ -82,6 +82,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-csv</artifactId>
+ </dependency>
<dependency>
<groupId>org.osgi</groupId>
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/EdeParser.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/EdeParser.java
index 7debb84..044655b 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/EdeParser.java
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/EdeParser.java
@@ -18,14 +18,84 @@ under the License.
*/
package org.apache.plc4x.java.bacnetip.ede;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.bacnetip.ede.model.Datapoint;
import org.apache.plc4x.java.bacnetip.ede.model.EdeModel;
+import org.apache.plc4x.java.bacnetip.field.BacNetIpField;
-import java.io.File;
+import java.io.*;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
public class EdeParser {
- public EdeModel parse(File knxprojFile) {
- return null;
+ public EdeModel parse(File edeFile) {
+ if(!edeFile.exists()) {
+ throw new PlcRuntimeException("EDE File at " + edeFile.getPath() + " doesn't exist.");
+ }
+ try {
+ Reader in = new FileReader(edeFile);
+ final CSVParser parser = CSVFormat.newFormat(';').parse(in);
+ final Iterator<CSVRecord> iterator = parser.iterator();
+
+ // Skip the header.
+ for(int i = 0; i < 5; i++) {
+ if(!iterator.hasNext()) {
+ throw new PlcRuntimeException("Invalid EDE file format");
+ }
+ iterator.next();
+ }
+ final CSVRecord layoutVersionRow = iterator.next();
+ // Check if the version of the layout is 3
+ if(!layoutVersionRow.get(1).equals("3")) {
+ throw new PlcRuntimeException("Unsupported EDE file layout version " + layoutVersionRow.get(2) +
+ ". Currently only version 3 is supported.");
+ }
+ // Just skip the next row.
+ iterator.next();
+ // Get the column names.
+ final CSVRecord columnNames = iterator.next();
+
+
+ Map<BacNetIpField, Datapoint> datapoints = new HashMap<>();
+ // Process the content.
+ iterator.forEachRemaining(record -> {
+ BacNetIpField address = new BacNetIpField(Long.parseLong(
+ record.get(1)), Integer.parseInt(record.get(3)), Long.parseLong(record.get(4)));
+ String keyName = (record.get(0).length() == 0) ? null : record.get(0);
+ String objectName = (record.get(2).length() == 0) ? null : record.get(2);
+ String description = (record.get(5).length() == 0) ? null : record.get(5);
+ Double defaultValue = (record.get(6).length() == 0) ? null : Double.valueOf(record.get(6).replace(",", "."));
+ Double minValue = (record.get(7).length() == 0) ? null : Double.valueOf(record.get(7).replace(",", "."));
+ Double maxValue = (record.get(8).length() == 0) ? null : Double.valueOf(record.get(8).replace(",", "."));
+ Boolean commandable = (record.get(9).length() == 0) ? null :
+ record.get(9).equalsIgnoreCase("Y") ? Boolean.TRUE : record.get(9).equalsIgnoreCase("N") ? Boolean.TRUE : null;
+ Boolean supportsCov = (record.get(10).length() == 0) ? null :
+ record.get(10).equalsIgnoreCase("Y") ? Boolean.TRUE : record.get(10).equalsIgnoreCase("N") ? Boolean.TRUE : null;
+ Double hiLimit = (record.get(11).length() == 0) ? null : Double.valueOf(record.get(11).replace(",", "."));
+ Double lowLimit = (record.get(12).length() == 0) ? null : Double.valueOf(record.get(12).replace(",", "."));
+ String stateTextReference = (record.get(13).length() == 0) ? null : record.get(13);
+ Integer unitCode = (record.get(14).length() == 0) ? null : Integer.valueOf(record.get(14));
+ Integer vendorSpecificAddress = (record.get(15).length() == 0) ? null : Integer.valueOf(record.get(15));
+ Datapoint datapoint = new Datapoint(address, keyName, objectName, description, defaultValue, minValue,
+ maxValue, commandable, supportsCov, hiLimit, lowLimit, stateTextReference, unitCode,
+ vendorSpecificAddress);
+ datapoints.put(address, datapoint);
+ });
+ return new EdeModel(datapoints);
+ } catch (IOException e) {
+ throw new PlcRuntimeException("Error parsing EDE file", e);
+ }
+
+ }
+
+ public static void main(String[] args) throws Exception {
+ EdeModel model = new EdeParser().parse(new File("/Users/christofer.dutz/Projects/Apache/PLC4X-Documents/BacNET/Merck/EDE-Files/F135/edeDataText3029.csv"));
+ System.out.println(model);
}
}
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/Datapoint.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/Datapoint.java
new file mode 100644
index 0000000..5599826
--- /dev/null
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/Datapoint.java
@@ -0,0 +1,135 @@
+/*
+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.java.bacnetip.ede.model;
+
+import org.apache.plc4x.java.api.value.*;
+import org.apache.plc4x.java.bacnetip.field.BacNetIpField;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Datapoint {
+
+ private final BacNetIpField address;
+ private final String keyName;
+ private final String objectName;
+ private final String description;
+ private final Double defaultValue;
+ private final Double minValue;
+ private final Double maxValue;
+ private final Boolean commandable;
+ private final Boolean supportsCov;
+ private final Double hiLimit;
+ private final Double lowLimit;
+ private final String stateTextReference;
+ private final Integer unitCode;
+ private final Integer vendorSpecificAddress;
+
+ public Datapoint(BacNetIpField address, String keyName, String objectName, String description, Double defaultValue, Double minValue, Double maxValue, Boolean commandable, Boolean supportsCov, Double hiLimit, Double lowLimit, String stateTextReference, Integer unitCode, Integer vendorSpecificAddress) {
+ this.address = address;
+ this.keyName = keyName;
+ this.objectName = objectName;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.commandable = commandable;
+ this.supportsCov = supportsCov;
+ this.hiLimit = hiLimit;
+ this.lowLimit = lowLimit;
+ this.stateTextReference = stateTextReference;
+ this.unitCode = unitCode;
+ this.vendorSpecificAddress = vendorSpecificAddress;
+ }
+
+ public BacNetIpField getAddress() {
+ return address;
+ }
+
+ public String getKeyName() {
+ return keyName;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Double getDefaultValue() {
+ return defaultValue;
+ }
+
+ public Double getMinValue() {
+ return minValue;
+ }
+
+ public Double getMaxValue() {
+ return maxValue;
+ }
+
+ public Boolean getCommandable() {
+ return commandable;
+ }
+
+ public Boolean getSupportsCov() {
+ return supportsCov;
+ }
+
+ public Double getHiLimit() {
+ return hiLimit;
+ }
+
+ public Double getLowLimit() {
+ return lowLimit;
+ }
+
+ public String getStateTextReference() {
+ return stateTextReference;
+ }
+
+ public Integer getUnitCode() {
+ return unitCode;
+ }
+
+ public Integer getVendorSpecificAddress() {
+ return vendorSpecificAddress;
+ }
+
+ public Map<String, PlcValue> toPlcValues() {
+ Map<String, PlcValue> values = new HashMap<>();
+ values.put("keyName", (keyName == null) ? null : new PlcString(keyName));
+ values.put("objectName", (objectName == null) ? null : new PlcString(objectName));
+ values.put("description", (description == null) ? null : new PlcString(description));
+ values.put("defaultValue", (defaultValue == null) ? null : new PlcDouble(defaultValue));
+ values.put("minValue", (minValue == null) ? null : new PlcDouble(minValue));
+ values.put("maxValue", (maxValue == null) ? null : new PlcDouble(maxValue));
+ values.put("commandable", (commandable == null) ? null : new PlcBoolean(commandable));
+ values.put("supportsCov", (supportsCov == null) ? null : new PlcBoolean(supportsCov));
+ values.put("hiLimit", (hiLimit == null) ? null : new PlcDouble(hiLimit));
+ values.put("lowLimit", (lowLimit == null) ? null : new PlcDouble(lowLimit));
+ values.put("stateTextReference", (stateTextReference == null) ? null : new PlcString(stateTextReference));
+ values.put("unitCode", (unitCode == null) ? null : new PlcInteger(unitCode));
+ values.put("vendorSpecificAddress", (vendorSpecificAddress == null) ? null : new PlcInteger(vendorSpecificAddress));
+ return values;
+ }
+
+}
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/EdeModel.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/EdeModel.java
index 6151fa2..b51b41d 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/EdeModel.java
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/ede/model/EdeModel.java
@@ -18,5 +18,20 @@ under the License.
*/
package org.apache.plc4x.java.bacnetip.ede.model;
+import org.apache.plc4x.java.bacnetip.field.BacNetIpField;
+
+import java.util.Map;
+
public class EdeModel {
+
+ private final Map<BacNetIpField, Datapoint> datapoints;
+
+ public EdeModel(Map<BacNetIpField, Datapoint> datapoints) {
+ this.datapoints = datapoints;
+ }
+
+ public Datapoint getDatapoint(BacNetIpField field) {
+ return datapoints.get(field);
+ }
+
}
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/field/BacNetIpField.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/field/BacNetIpField.java
index 7432827..a76a33a 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/field/BacNetIpField.java
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/field/BacNetIpField.java
@@ -18,6 +18,8 @@ under the License.
*/
package org.apache.plc4x.java.bacnetip.field;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
import org.apache.plc4x.java.api.model.PlcField;
@@ -80,6 +82,34 @@ public class BacNetIpField implements PlcField {
}
@Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof BacNetIpField)) {
+ return false;
+ }
+
+ BacNetIpField that = (BacNetIpField) o;
+
+ return new EqualsBuilder()
+ .append(getDeviceIdentifier(), that.getDeviceIdentifier())
+ .append(getObjectType(), that.getObjectType())
+ .append(getObjectInstance(), that.getObjectInstance())
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(getDeviceIdentifier())
+ .append(getObjectType())
+ .append(getObjectInstance())
+ .toHashCode();
+ }
+
+ @Override
public String toString() {
return new ToStringBuilder(this)
.append("deviceIdentifier", deviceIdentifier)
diff --git a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/protocol/PassiveBacNetIpProtocolLogic.java b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/protocol/PassiveBacNetIpProtocolLogic.java
index 0f2cf9b..6973d6c 100644
--- a/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/protocol/PassiveBacNetIpProtocolLogic.java
+++ b/sandbox/test-java-bacnetip-driver/src/main/java/org/apache/plc4x/java/bacnetip/protocol/PassiveBacNetIpProtocolLogic.java
@@ -27,16 +27,18 @@ import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.api.value.PlcString;
+import org.apache.plc4x.java.api.value.PlcStruct;
import org.apache.plc4x.java.api.value.PlcValue;
import org.apache.plc4x.java.bacnetip.configuration.PassiveBacNetIpConfiguration;
import org.apache.plc4x.java.bacnetip.ede.EdeParser;
+import org.apache.plc4x.java.bacnetip.ede.model.Datapoint;
import org.apache.plc4x.java.bacnetip.ede.model.EdeModel;
import org.apache.plc4x.java.bacnetip.field.BacNetIpField;
import org.apache.plc4x.java.bacnetip.readwrite.*;
import org.apache.plc4x.java.spi.ConversationContext;
import org.apache.plc4x.java.spi.Plc4xProtocolBase;
import org.apache.plc4x.java.spi.configuration.HasConfiguration;
-import org.apache.plc4x.java.spi.generation.ReadBuffer;
import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionEvent;
import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionResponse;
import org.apache.plc4x.java.spi.messages.InternalPlcSubscriptionRequest;
@@ -80,6 +82,11 @@ public class PassiveBacNetIpProtocolLogic extends Plc4xProtocolBase<BVLC> implem
}
@Override
+ public void onConnect(ConversationContext<BVLC> context) {
+ context.fireConnected();
+ }
+
+ @Override
public void close(ConversationContext<BVLC> context) {
// Nothing to do here ...
}
@@ -112,22 +119,29 @@ public class PassiveBacNetIpProtocolLogic extends Plc4xProtocolBase<BVLC> implem
long objectInstance = valueChange.getIssueConfirmedNotificationsInstanceNumber();
BacNetIpField curField = new BacNetIpField(deviceIdentifier, objectType, objectInstance);
- System.out.println("Value change for " + curField.toString());
-
+ // The actual value change is in the notifications ... iterate throught them to get it.
for (BACnetTagWithContent notification : valueChange.getNotifications()) {
+ // These are value change notifications. Ignore the rest.
if(notification.getPropertyIdentifier()[0] == (short) 0x55) {
final BACnetTag baCnetTag = notification.getValue();
- if(baCnetTag instanceof BACnetTagApplicationReal) {
- System.out.println(baCnetTag);
+ final PlcValue plcValue = baCnetTag.toPlcValue();
+
+ // Initialize an enriched version of the PlcStruct.
+ final Map<String, PlcValue> enrichedPlcValue = new HashMap<>();
+ enrichedPlcValue.put("address", new PlcString(toString(curField)));
+ // Add all of the existing attributes.
+ enrichedPlcValue.putAll(plcValue.getStruct());
+
+ // Use the information in the edeModel to enrich the information.
+ if(edeModel != null) {
+ final Datapoint datapoint = edeModel.getDatapoint(curField);
+ if(datapoint != null) {
+ // Add all the attributes from the ede file.
+ enrichedPlcValue.putAll(datapoint.toPlcValues());
+ }
}
- }
- // Use the information in the edeModel to enrich the information.
- if(edeModel != null) {
- // TODO: Implement.
- }
- // Else just output the information without enriching it.
- else {
- // TODO: Implement.
+ // Send out the enriched event.
+ publishEvent(curField, new PlcStruct(enrichedPlcValue));
}
}
}
@@ -196,22 +210,20 @@ public class PassiveBacNetIpProtocolLogic extends Plc4xProtocolBase<BVLC> implem
consumerIdMap.remove(consumerRegistration.getConsumerHash());
}
- protected void publishEvent(String name, PlcValue plcValue) {
+ protected void publishEvent(BacNetIpField field, PlcValue plcValue) {
// Create a subscription event from the input.
final PlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(Instant.now(),
- Collections.singletonMap(name, Pair.of(PlcResponseCode.OK, plcValue)));
+ Collections.singletonMap("event", Pair.of(PlcResponseCode.OK, plcValue)));
// Send the subscription event to all listeners.
for (Consumer<PlcSubscriptionEvent> consumer : consumerIdMap.values()) {
+ // TODO: Check if the subscription matches the current field ..
consumer.accept(event);
}
}
- /*protected PlcValue toPlcValue(BACnetTag tag) {
- if(tag instanceof BACnetTagApplicationReal) {
- BACnetTagApplicationReal baCnetTagApplicationReal = (BACnetTagApplicationReal) tag;
- baCnetTagApplicationReal.getData();
- }
- }*/
+ private String toString(BacNetIpField field) {
+ return field.getDeviceIdentifier() + "/" + field.getObjectType() + "/" + field.getObjectInstance();
+ }
}
diff --git a/sandbox/test-java-bacnetip-driver/src/test/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriverManual.java b/sandbox/test-java-bacnetip-driver/src/test/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriverManual.java
index 4e6f903..92b3802 100644
--- a/sandbox/test-java-bacnetip-driver/src/test/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriverManual.java
+++ b/sandbox/test-java-bacnetip-driver/src/test/java/org/apache/plc4x/java/bacnetip/PassiveBacNetIpDriverManual.java
@@ -19,13 +19,21 @@ under the License.
package org.apache.plc4x.java.bacnetip;
import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
+import org.apache.plc4x.java.api.value.PlcStruct;
+import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionEvent;
public class PassiveBacNetIpDriverManual {
public static void main(String[] args) throws Exception {
final PassiveBacNetIpDriver driver = new PassiveBacNetIpDriver();
- final PlcConnection connection = driver.getConnection("bacnet-ip:pcap:///Users/christofer.dutz/Projects/Apache/PLC4X-Documents/BacNET/Merck/Captures/BACnet.pcapng");
+ final PlcConnection connection = driver.getConnection("bacnet-ip:pcap:///Users/christofer.dutz/Projects/Apache/PLC4X-Documents/BacNET/Merck/Captures/BACnet.pcapng?ede-file-path=/Users/christofer.dutz/Projects/Apache/PLC4X-Documents/BacNET/Merck/EDE-Files/M32/edeDataText3014.csv");
connection.connect();
+ final PlcSubscriptionResponse subscriptionResponse = connection.subscriptionRequestBuilder().addEventField("Hurz", "*/*/*").build().execute().get();
+ subscriptionResponse.getSubscriptionHandle("Hurz").register(plcSubscriptionEvent -> {
+ PlcStruct plcStruct = (PlcStruct) ((DefaultPlcSubscriptionEvent) plcSubscriptionEvent).getValues().get("event").getRight();
+ System.out.println(plcStruct);
+ });
}
}