This is an automated email from the ASF dual-hosted git repository.
ldywicki 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 862efa519b Draft of metadata API with timestamp tracking capabilities.
862efa519b is described below
commit 862efa519bce134348a4ef40f0bea4e0795992a0
Author: Łukasz Dywicki <[email protected]>
AuthorDate: Fri Aug 9 19:46:44 2024 +0200
Draft of metadata API with timestamp tracking capabilities.
Signed-off-by: Łukasz Dywicki <[email protected]>
---
.../{PlcTagResponse.java => PlcMetadataKeys.java} | 24 +-
.../plc4x/java/api/messages/PlcTagResponse.java | 6 +
.../apache/plc4x/java/api/metadata/Metadata.java | 89 ++++++
.../plc4x/java/api/metadata/time/TimeSource.java} | 20 +-
.../plc4x/java/ads/protocol/AdsProtocolLogic.java | 65 ++++-
.../apache/plc4x/java/opcua/OpcMetadataKeys.java} | 25 +-
.../java/opcua/protocol/OpcuaProtocolLogic.java | 41 ++-
.../opcua/protocol/OpcuaSubscriptionHandle.java | 17 +-
.../plc4x/java/opcua/tag/OpcuaQualityStatus.java | 58 ++++
.../apache/plc4x/java/s7/events/S7AlarmEvent.java | 317 +++++++++++----------
.../apache/plc4x/java/s7/events/S7CyclicEvent.java | 217 +++++---------
.../apache/plc4x/java/s7/events/S7EventBase.java | 59 ++++
.../apache/plc4x/java/s7/events/S7ModeEvent.java | 18 +-
.../apache/plc4x/java/s7/events/S7SysEvent.java | 55 ++--
.../apache/plc4x/java/s7/events/S7UserEvent.java | 13 +-
.../s7/readwrite/protocol/S7ProtocolLogic.java | 6 +-
.../java/spi/messages/DefaultPlcReadResponse.java | 30 ++
.../spi/messages/DefaultPlcSubscriptionEvent.java | 13 +-
.../java/spi/messages/DefaultPlcWriteResponse.java | 30 ++
.../plc4x/java/spi/metadata/DefaultMetadata.java | 132 +++++++++
.../driver/internal/validator/ApiValidator.java | 5 +
.../test/driver/xmlunit/SkipAttributeFilter.java} | 22 +-
.../driver/xmlunit/SkipDifferenceEvaluator.java | 56 ++++
.../resources/protocols/ads/DriverTestsuite.xml | 12 +
24 files changed, 907 insertions(+), 423 deletions(-)
diff --git
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java
similarity index 61%
copy from
plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
copy to
plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java
index 3aa2d642de..e88ca207f3 100644
---
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
+++
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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
@@ -16,26 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.java.api.messages;
-import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.api.types.PlcResponseCode;
+package org.apache.plc4x.java.api.messages;
-import java.util.Collection;
+import org.apache.plc4x.java.api.metadata.Metadata.Key;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
/**
- * Base type for all response messages sent as response for a prior request
- * from a plc to the plc4x system.
+ * High level definition of common metadata keys which can occur across
multiple drivers.
*/
-public interface PlcTagResponse extends PlcResponse {
-
- @Override
- PlcTagRequest getRequest();
-
- Collection<String> getTagNames();
+public interface PlcMetadataKeys {
- PlcTag getTag(String name);
+ Key<Long> TIMESTAMP = Key.of("timestamp", Long.class);
+ Key<TimeSource> TIMESTAMP_SOURCE = Key.of("timestamp_source",
TimeSource.class);
- PlcResponseCode getResponseCode(String name);
+ Key<Long> RECEIVE_TIMESTAMP = Key.of("receive_timestamp", Long.class);
}
diff --git
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
index 3aa2d642de..1feae9f3bb 100644
---
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
+++
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
@@ -18,6 +18,7 @@
*/
package org.apache.plc4x.java.api.messages;
+import org.apache.plc4x.java.api.metadata.Metadata;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
@@ -38,4 +39,9 @@ public interface PlcTagResponse extends PlcResponse {
PlcResponseCode getResponseCode(String name);
+ /**
+ * Returns tag level metadata information.
+ */
+ Metadata getTagMetadata(String name);
+
}
diff --git
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/Metadata.java
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/Metadata.java
new file mode 100644
index 0000000000..484fad7148
--- /dev/null
+++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/Metadata.java
@@ -0,0 +1,89 @@
+/*
+ * 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.api.metadata;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public interface Metadata {
+
+ Metadata EMPTY = new Metadata() {
+ @Override
+ public Set<Key<?>> keys() {
+ return Set.of();
+ }
+
+ @Override
+ public Map<Key<?>, Object> entries() {
+ return Map.of();
+ }
+
+ @Override
+ public Object getValue(Key<?> key) {
+ return null;
+ }
+
+ };
+
+ class Key<T> {
+
+ private final String key;
+ private final Class<T> type;
+
+ protected Key(String key, Class<T> type) {
+ this.key = key;
+ this.type = type;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public boolean validate(Object value) {
+ return type.isInstance(value);
+ }
+
+ public static <T> Key<T> of(String key, Class<T> type) {
+ return new Key<>(key, type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Key)) {
+ return false;
+ }
+ Key<?> key1 = (Key<?>) o;
+ return Objects.equals(getKey(), key1.getKey()) &&
Objects.equals(type, key1.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getKey(), type);
+ }
+ }
+
+ Set<Key<?>> keys();
+ Map<Key<?>, Object> entries();
+ Object getValue(Key<?> key);
+}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java
similarity index 63%
copy from
plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
copy to
plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java
index d9f01d0dbd..84e5843635 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
+++
b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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
@@ -16,14 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.java.s7.events;
-import org.apache.plc4x.java.s7.readwrite.S7PayloadDiagnosticMessage;
+package org.apache.plc4x.java.api.metadata.time;
-public class S7UserEvent extends S7SysEvent {
+public enum TimeSource {
+
+ // Time information is assumed by PLC4X itself
+ ASSUMPTION,
+ // Time comes from software layer, kernel driver and similar
+ SOFTWARE,
+ // Time can is confronted through hardware i.e. microcontroller
+ HARDWARE,
+ // Other source of time which fall into separate truthiness category
+ OTHER
- public S7UserEvent(S7PayloadDiagnosticMessage payload) {
- super(payload);
- map.put(Fields.TYPE.name(), "USER");
- }
}
diff --git
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
index 08eaa141d0..7a58931ae7 100644
---
a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
+++
b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
@@ -34,6 +34,9 @@ import org.apache.plc4x.java.api.exceptions.PlcException;
import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
import org.apache.plc4x.java.api.model.*;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.types.PlcSubscriptionType;
@@ -718,8 +721,13 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
.check(userdata -> userdata.getInvokeId() ==
amsPacket.getInvokeId())
.only(AdsReadResponse.class)
.handle(response -> {
+ // result metadata
+ Metadata metadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP,
System.currentTimeMillis())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE,
TimeSource.ASSUMPTION)
+ .build();
if (response.getResult() == ReturnCode.OK) {
- final PlcReadResponse plcReadResponse =
convertToPlc4xReadResponse(readRequest, Map.of((AdsTag)
readRequest.getTags().get(0), directAdsTag), response);
+ final PlcReadResponse plcReadResponse =
convertToPlc4xReadResponse(readRequest, Map.of((AdsTag)
readRequest.getTags().get(0), directAdsTag), response, metadata);
// Convert the response from the PLC into a PLC4X Response
...
future.complete(plcReadResponse);
} else {
@@ -791,8 +799,12 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
.check(userdata -> userdata.getInvokeId() ==
amsPacket.getInvokeId())
.only(AdsReadWriteResponse.class)
.handle(response -> {
+ Metadata metadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP,
System.currentTimeMillis())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE,
TimeSource.ASSUMPTION)
+ .build();
if (response.getResult() == ReturnCode.OK) {
- final PlcReadResponse plcReadResponse =
convertToPlc4xReadResponse(readRequest, resolvedTags, response);
+ final PlcReadResponse plcReadResponse =
convertToPlc4xReadResponse(readRequest, resolvedTags, response, metadata);
// Convert the response from the PLC into a PLC4X Response
...
future.complete(plcReadResponse);
} else if (response.getResult() ==
ReturnCode.ADSERR_DEVICE_INVALIDSIZE) {
@@ -807,8 +819,9 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
return future;
}
- protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest
readRequest, Map<AdsTag, DirectAdsTag> resolvedTags, AmsPacket adsData) {
+ protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest
readRequest, Map<AdsTag, DirectAdsTag> resolvedTags, AmsPacket adsData,
Metadata responseMetadata) {
ReadBuffer readBuffer = null;
+ Map<String, Metadata> metadata = new HashMap<>();
Map<String, PlcResponseCode> responseCodes = new HashMap<>();
// Read the response codes first
@@ -841,6 +854,7 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
if (readBuffer != null) {
Map<String, PlcResponseItem<PlcValue>> values = new HashMap<>();
for (String tagName : readRequest.getTagNames()) {
+ metadata.put(tagName, new
DefaultMetadata.Builder(responseMetadata).build());
// If the response-code was anything but OK, we don't need to
parse the payload.
if(responseCodes.get(tagName) != PlcResponseCode.OK) {
values.put(tagName, new
DefaultPlcResponseItem<>(responseCodes.get(tagName), null));
@@ -851,7 +865,7 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
values.put(tagName, parseResponseItem(directAdsTag,
readBuffer));
}
}
- return new DefaultPlcReadResponse(readRequest, values);
+ return new DefaultPlcReadResponse(readRequest, values, metadata);
}
return null;
}
@@ -1071,8 +1085,13 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
.check(userdata -> userdata.getInvokeId() ==
amsPacket.getInvokeId())
.only(AdsWriteResponse.class)
.handle(response -> {
+ // result metadata
+ Metadata eventMetadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP,
System.currentTimeMillis())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE,
TimeSource.ASSUMPTION)
+ .build();
if (response.getResult() == ReturnCode.OK) {
- final PlcWriteResponse plcWriteResponse =
convertToPlc4xWriteResponse(writeRequest, Collections.singletonMap((AdsTag)
writeRequest.getTag(tagName), directAdsTag), response);
+ final PlcWriteResponse plcWriteResponse =
convertToPlc4xWriteResponse(writeRequest, Collections.singletonMap((AdsTag)
writeRequest.getTag(tagName), directAdsTag), response, eventMetadata);
// Convert the response from the PLC into a PLC4X Response
...
future.complete(plcWriteResponse);
} else {
@@ -1149,8 +1168,14 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
.check(userdata -> userdata.getInvokeId() ==
amsPacket.getInvokeId())
.only(AdsReadWriteResponse.class)
.handle(response -> {
+ // result metadata
+ Metadata eventMetadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP,
System.currentTimeMillis())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE,
TimeSource.ASSUMPTION)
+ .build();
+
if (response.getResult() == ReturnCode.OK) {
- final PlcWriteResponse plcWriteResponse =
convertToPlc4xWriteResponse(writeRequest, resolvedTags, response);
+ final PlcWriteResponse plcWriteResponse =
convertToPlc4xWriteResponse(writeRequest, resolvedTags, response,
eventMetadata);
// Convert the response from the PLC into a PLC4X Response
...
future.complete(plcWriteResponse);
} else {
@@ -1244,8 +1269,9 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
}
}
- protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest
writeRequest, Map<AdsTag, DirectAdsTag> resolvedTags, AmsPacket adsData) {
+ protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest
writeRequest, Map<AdsTag, DirectAdsTag> resolvedTags, AmsPacket adsData,
Metadata eventMtadata) {
Map<String, PlcResponseCode> responseCodes = new HashMap<>();
+ Map<String, Metadata> metadata = new HashMap<>();
if (adsData instanceof AdsWriteResponse) {
AdsWriteResponse adsWriteResponse = (AdsWriteResponse) adsData;
responseCodes.put(writeRequest.getTagNames().stream().findFirst().orElse(""),
@@ -1256,6 +1282,9 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
// When parsing a multi-item response, the error codes of each
items come
// in sequence and then come the values.
for (String tagName : writeRequest.getTagNames()) {
+ // result metadata
+ metadata.put(tagName, eventMtadata);
+
AdsTag adsTag = (AdsTag) writeRequest.getTag(tagName);
// Skip invalid addresses.
if(resolvedTags.get(adsTag) == null) {
@@ -1271,7 +1300,7 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
}
}
- return new DefaultPlcWriteResponse(writeRequest, responseCodes);
+ return new DefaultPlcWriteResponse(writeRequest, responseCodes,
metadata);
}
@Override
@@ -1493,9 +1522,16 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
if (msg.getUserdata() instanceof AdsDeviceNotificationRequest) {
AdsDeviceNotificationRequest notificationData =
(AdsDeviceNotificationRequest) msg.getUserdata();
List<AdsStampHeader> stamps =
notificationData.getAdsStampHeaders();
+ long receiveTs = System.currentTimeMillis();
for (AdsStampHeader stamp : stamps) {
// convert Windows FILETIME format to unix epoch
long unixEpochTimestamp =
stamp.getTimestamp().divide(BigInteger.valueOf(10000L)).longValue() -
11644473600000L;
+ // result metadata
+ Metadata eventMetadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP, receiveTs)
+ .put(PlcMetadataKeys.TIMESTAMP, unixEpochTimestamp)
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.SOFTWARE)
+ .build();
List<AdsNotificationSample> samples =
stamp.getAdsNotificationSamples();
for (AdsNotificationSample sample : samples) {
long handle = sample.getNotificationHandle();
@@ -1503,10 +1539,12 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
for (PlcSubscriptionHandle subscriptionHandle :
registration.getSubscriptionHandles()) {
if (subscriptionHandle instanceof
AdsSubscriptionHandle) {
AdsSubscriptionHandle adsHandle =
(AdsSubscriptionHandle) subscriptionHandle;
- if (adsHandle.getNotificationHandle() ==
handle)
- consumers.get(registration).accept(
- new
DefaultPlcSubscriptionEvent(Instant.ofEpochMilli(unixEpochTimestamp),
-
convertSampleToPlc4XResult(adsHandle, sample.getData())));
+ if (adsHandle.getNotificationHandle() ==
handle) {
+ Map<String, Metadata> metadata = new
HashMap<>();
+ Instant timestamp =
Instant.ofEpochMilli(unixEpochTimestamp);
+ DefaultPlcSubscriptionEvent event = new
DefaultPlcSubscriptionEvent(timestamp, convertSampleToPlc4XResult(adsHandle,
sample.getData(), metadata, eventMetadata));
+ consumers.get(registration).accept(event);
+ }
}
}
}
@@ -1515,12 +1553,13 @@ public class AdsProtocolLogic extends
Plc4xProtocolBase<AmsTCPPacket> implements
}
}
- private Map<String, PlcResponseItem<PlcValue>>
convertSampleToPlc4XResult(AdsSubscriptionHandle subscriptionHandle, byte[]
data) throws
+ private Map<String, PlcResponseItem<PlcValue>>
convertSampleToPlc4XResult(AdsSubscriptionHandle subscriptionHandle, byte[]
data, Map<String, Metadata> tagMetadata, Metadata metadata) throws
ParseException {
Map<String, PlcResponseItem<PlcValue>> values = new HashMap<>();
ReadBufferByteBased readBuffer = new ReadBufferByteBased(data,
ByteOrder.LITTLE_ENDIAN);
values.put(subscriptionHandle.getTagName(), new
DefaultPlcResponseItem<>(PlcResponseCode.OK,
DataItem.staticParse(readBuffer,
getPlcValueTypeForAdsDataType(subscriptionHandle.getAdsDataType()),
data.length)));
+ tagMetadata.put(subscriptionHandle.getTagName(), new
DefaultMetadata.Builder(metadata).build());
return values;
}
diff --git
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java
similarity index 56%
copy from
plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
copy to
plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java
index 3aa2d642de..713081159c 100644
---
a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcTagResponse.java
+++
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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
@@ -16,26 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.java.api.messages;
-import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.api.types.PlcResponseCode;
+package org.apache.plc4x.java.opcua;
-import java.util.Collection;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.api.metadata.Metadata.Key;
+import org.apache.plc4x.java.opcua.tag.OpcuaQualityStatus;
/**
- * Base type for all response messages sent as response for a prior request
- * from a plc to the plc4x system.
+ * OPC UA level metadata keys.
*/
-public interface PlcTagResponse extends PlcResponse {
+public interface OpcMetadataKeys {
- @Override
- PlcTagRequest getRequest();
+ Key<OpcuaQualityStatus> QUALITY = Metadata.Key.of("opcua_quality",
OpcuaQualityStatus.class);
- Collection<String> getTagNames();
-
- PlcTag getTag(String name);
-
- PlcResponseCode getResponseCode(String name);
+ Key<Long> SERVER_TIMESTAMP = Metadata.Key.of("opcua_server_timestamp",
Long.class);
+ Key<Long> SOURCE_TIMESTAMP = Metadata.Key.of("opcua_source_timestamp",
Long.class);
}
diff --git
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
index 91112aef60..0ccf130a5b 100644
---
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
+++
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaProtocolLogic.java
@@ -26,18 +26,23 @@ import
org.apache.plc4x.java.api.authentication.PlcAuthentication;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.types.PlcValueType;
import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.opcua.OpcMetadataKeys;
import org.apache.plc4x.java.opcua.config.OpcuaConfiguration;
import org.apache.plc4x.java.opcua.context.Conversation;
import org.apache.plc4x.java.opcua.context.OpcuaDriverContext;
import org.apache.plc4x.java.opcua.context.SecureChannel;
import org.apache.plc4x.java.opcua.readwrite.*;
import org.apache.plc4x.java.opcua.tag.OpcuaPlcTagHandler;
+import org.apache.plc4x.java.opcua.tag.OpcuaQualityStatus;
import org.apache.plc4x.java.opcua.tag.OpcuaTag;
import org.apache.plc4x.java.spi.ConversationContext;
import org.apache.plc4x.java.spi.Plc4xProtocolBase;
@@ -210,7 +215,7 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
List<ExtensionObjectDefinition> readValueArray = new
ArrayList<>(request.getTagNames().size());
Iterator<String> iterator = request.getTagNames().iterator();
- Map<String, PlcTag> tagMap = new HashMap<>();
+ Map<String, PlcTag> tagMap = new LinkedHashMap<>();
for (int i = 0; i < request.getTagNames().size(); i++) {
String tagName = iterator.next();
// TODO: We need to check that the tag-return-code is OK as it
could also be INVALID_TAG
@@ -237,7 +242,13 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
transaction.submit(() -> {
conversation.submit(opcuaReadRequest,
ReadResponse.class).whenComplete((response, error) -> bridge(transaction,
future, response, error));
});
- return future.thenApply(response -> new
DefaultPlcReadResponse(request, readResponse(request.getTagNames(), tagMap,
response.getResults())));
+ return future.thenApply(response -> {
+ Metadata responseMetadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP,
System.currentTimeMillis())
+ .build();
+ Map<String, Metadata> metadata = new LinkedHashMap<>();
+ return new DefaultPlcReadResponse(request, readResponse(tagMap,
response.getResults(), metadata, responseMetadata), metadata);
+ });
}
static NodeId generateNodeId(OpcuaTag tag) {
@@ -262,15 +273,16 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
return nodeId;
}
- public Map<String, PlcResponseItem<PlcValue>>
readResponse(LinkedHashSet<String> tagNames, Map<String, PlcTag> tagMap,
List<DataValue> results) {
+ public Map<String, PlcResponseItem<PlcValue>> readResponse(Map<String,
PlcTag> tagMap, List<DataValue> results, Map<String, Metadata> metadata,
Metadata responseMetadata) {
PlcResponseCode responseCode = null; // initialize variable
Map<String, PlcResponseItem<PlcValue>> response = new HashMap<>();
- int count = 0;
- for (String tagName : tagNames) {
+ int index = 0;
+ for (String tagName : tagMap.keySet()) {
PlcTag tag = tagMap.get(tagName);
PlcValue value = null;
- if (results.get(count).getValueSpecified()) {
- Variant variant = results.get(count).getValue();
+ DataValue dataValue = results.get(index++);
+ if (dataValue.getValueSpecified()) {
+ Variant variant = dataValue.getValue();
LOGGER.trace("Response of type {}",
variant.getClass().toString());
if (variant instanceof VariantBoolean) {
byte[] array = ((VariantBoolean) variant).getValue();
@@ -423,12 +435,20 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
responseCode = PlcResponseCode.OK;
}
} else {
- StatusCode statusCode = results.get(count).getStatusCode();
+ StatusCode statusCode = dataValue.getStatusCode();
responseCode = mapOpcStatusCode(statusCode.getStatusCode(),
PlcResponseCode.UNSUPPORTED);
- LOGGER.error("Error while reading value from OPC UA server
error code: {}", results.get(count).getStatusCode().toString());
+ LOGGER.error("Error while reading value from OPC UA server
error code:- " + dataValue.getStatusCode().toString());
}
- count++;
+
+ Metadata tagMetadata = new
DefaultMetadata.Builder(responseMetadata)
+ .put(OpcMetadataKeys.QUALITY, new
OpcuaQualityStatus(dataValue.getStatusCode()))
+ .put(OpcMetadataKeys.SERVER_TIMESTAMP,
dataValue.getServerTimestamp())
+ .put(OpcMetadataKeys.SOURCE_TIMESTAMP,
dataValue.getSourceTimestamp())
+ .put(PlcMetadataKeys.TIMESTAMP, dataValue.getServerTimestamp())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.SOFTWARE)
+ .build();
response.put(tagName, new DefaultPlcResponseItem<>(responseCode,
value));
+ metadata.put(tagName, tagMetadata);
}
return response;
}
@@ -880,4 +900,5 @@ public class OpcuaProtocolLogic extends
Plc4xProtocolBase<OpcuaAPU> implements H
transaction.endRequest();
}
}
+
}
diff --git
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandle.java
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandle.java
index ab241b0411..4889480f3c 100644
---
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandle.java
+++
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/protocol/OpcuaSubscriptionHandle.java
@@ -24,8 +24,11 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import org.apache.plc4x.java.api.messages.PlcMetadataKeys;
import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.value.PlcValue;
@@ -34,7 +37,6 @@ import org.apache.plc4x.java.opcua.tag.OpcuaTag;
import org.apache.plc4x.java.opcua.readwrite.*;
import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionEvent;
import org.apache.plc4x.java.spi.messages.utils.PlcResponseItem;
-import org.apache.plc4x.java.spi.messages.utils.PlcTagItem;
import org.apache.plc4x.java.spi.model.DefaultPlcConsumerRegistration;
import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionTag;
import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle;
@@ -260,17 +262,22 @@ public class OpcuaSubscriptionHandle extends
DefaultPlcSubscriptionHandle {
* @param values - array of data values to be sent to the client.
*/
private void onSubscriptionValue(MonitoredItemNotification[] values) {
- LinkedHashSet<String> tagNameList = new LinkedHashSet<>();
+ long receiveTs = System.currentTimeMillis();
+ Metadata responseMetadata = new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.RECEIVE_TIMESTAMP, receiveTs)
+ .build();
+
List<DataValue> dataValues = new ArrayList<>(values.length);
Map<String, PlcTag> tagMap = new LinkedHashMap<>();
for (MonitoredItemNotification value : values) {
String tagName = tagNames.get((int) value.getClientHandle() - 1);
- tagNameList.add(tagName);
tagMap.put(tagName, subscriptionRequest.getTag(tagName).getTag());
dataValues.add(value.getValue());
}
- Map<String, PlcResponseItem<PlcValue>> tags =
plcSubscriber.readResponse(tagNameList, tagMap, dataValues);
- final PlcSubscriptionEvent event = new
DefaultPlcSubscriptionEvent(Instant.now(), tags);
+
+ Map<String, Metadata> metadata = new HashMap<>();
+ Map<String, PlcResponseItem<PlcValue>> tags =
plcSubscriber.readResponse(tagMap, dataValues, metadata, responseMetadata);
+ final PlcSubscriptionEvent event = new
DefaultPlcSubscriptionEvent(Instant.now(), tags, metadata);
consumers.forEach(plcSubscriptionEventConsumer ->
plcSubscriptionEventConsumer.accept(event));
}
diff --git
a/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/tag/OpcuaQualityStatus.java
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/tag/OpcuaQualityStatus.java
new file mode 100644
index 0000000000..1500e21099
--- /dev/null
+++
b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/tag/OpcuaQualityStatus.java
@@ -0,0 +1,58 @@
+/*
+ * 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.opcua.tag;
+
+import org.apache.plc4x.java.opcua.readwrite.StatusCode;
+
+public final class OpcuaQualityStatus {
+
+ private static final long STATUS_MASK = 0xC0000000L;
+ private static final long STATUS_GOOD = 0x00000000L;
+ private static final long STATUS_UNCERTAIN = 0x40000000L;
+ private static final long STATUS_BAD = 0x80000000L;
+
+ private final StatusCode statusCode;
+
+ public OpcuaQualityStatus(StatusCode statusCode) {
+ this.statusCode = statusCode;
+ }
+
+ public boolean isGood() {
+ return (statusCode.getStatusCode() & STATUS_MASK) == STATUS_GOOD;
+ }
+
+ public boolean isBad() {
+ return (statusCode.getStatusCode() & STATUS_MASK) == STATUS_BAD;
+ }
+
+ public boolean isUncertain() {
+ return (statusCode.getStatusCode() & STATUS_MASK) == STATUS_UNCERTAIN;
+ }
+
+ @Override
+ public String toString() {
+ if (isGood()) {
+ return "good";
+ } else if (isBad()) {
+ return "bad";
+ }
+ return "uncertain";
+ }
+}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7AlarmEvent.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7AlarmEvent.java
index 931bfab0a8..5d08cc6104 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7AlarmEvent.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7AlarmEvent.java
@@ -18,6 +18,7 @@
*/
package org.apache.plc4x.java.s7.events;
+import java.util.Collections;
import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.model.PlcTag;
@@ -33,7 +34,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class S7AlarmEvent implements S7Event {
+public class S7AlarmEvent extends S7EventBase {
public enum Fields {
@@ -130,172 +131,19 @@ public class S7AlarmEvent implements S7Event {
}
- private final Instant timeStamp;
private final Map<String, Object> map;
- public S7AlarmEvent(Object obj) {
- this.map = new HashMap<>();
- if (obj instanceof S7PayloadAlarmAckInd) {
- AlarmMessageAckPushType msg = ((S7PayloadAlarmAckInd)
obj).getAlarmMessage();
- DateAndTime dt = msg.getTimeStamp();
- int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 :
dt.getYear() + 2000;
- LocalDateTime ldt = LocalDateTime.of(year,
- dt.getMonth(),
- dt.getDay(),
- dt.getHour(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMsec() * 1000000);
- this.timeStamp = ldt.toInstant(ZoneOffset.UTC);
- map.put(S7SysEvent.Fields.TIMESTAMP.name(), this.timeStamp);
-
- List<AlarmMessageAckObjectPushType> items =
msg.getMessageObjects();
- for (AlarmMessageAckObjectPushType item : items) {
- map.put(Fields.EVENT_ID.name(), item.getEventId());
- map.put(Fields.TYPE.name(), "ALARMACK_IND");
- map.put(Fields.ASSOCIATED_VALUES.name(),
item.getNumberOfValues());
-
- map.put(Fields.SIG_1_DATA_GOING.name(),
item.getAckStateGoing().getSIG_1());
- map.put(Fields.SIG_2_DATA_GOING.name(),
item.getAckStateGoing().getSIG_2());
- map.put(Fields.SIG_3_DATA_GOING.name(),
item.getAckStateGoing().getSIG_3());
- map.put(Fields.SIG_4_DATA_GOING.name(),
item.getAckStateGoing().getSIG_4());
- map.put(Fields.SIG_5_DATA_GOING.name(),
item.getAckStateGoing().getSIG_5());
- map.put(Fields.SIG_6_DATA_GOING.name(),
item.getAckStateGoing().getSIG_6());
- map.put(Fields.SIG_7_DATA_GOING.name(),
item.getAckStateGoing().getSIG_7());
- map.put(Fields.SIG_8_DATA_GOING.name(),
item.getAckStateGoing().getSIG_8());
-
- map.put(Fields.SIG_1_DATA_COMING.name(),
item.getAckStateComing().getSIG_1());
- map.put(Fields.SIG_2_DATA_COMING.name(),
item.getAckStateComing().getSIG_2());
- map.put(Fields.SIG_3_DATA_COMING.name(),
item.getAckStateComing().getSIG_3());
- map.put(Fields.SIG_4_DATA_COMING.name(),
item.getAckStateComing().getSIG_4());
- map.put(Fields.SIG_5_DATA_COMING.name(),
item.getAckStateComing().getSIG_5());
- map.put(Fields.SIG_6_DATA_COMING.name(),
item.getAckStateComing().getSIG_6());
- map.put(Fields.SIG_7_DATA_COMING.name(),
item.getAckStateComing().getSIG_7());
- map.put(Fields.SIG_8_DATA_COMING.name(),
item.getAckStateComing().getSIG_8());
- }
-
- } else {
-
- AlarmMessagePushType msg = null;
-
- if (obj instanceof S7PayloadAlarm8) {
- msg = ((S7PayloadAlarm8) obj).getAlarmMessage();
- } else if (obj instanceof S7PayloadNotify) {
- msg = ((S7PayloadNotify) obj).getAlarmMessage();
- } else if (obj instanceof S7PayloadAlarmSQ) {
- msg = ((S7PayloadAlarmSQ) obj).getAlarmMessage();
- } else if (obj instanceof S7PayloadAlarmS) {
- msg = ((S7PayloadAlarmS) obj).getAlarmMessage();
- } else if (obj instanceof S7PayloadNotify8) {
- msg = ((S7PayloadNotify8) obj).getAlarmMessage();
- } else {
- throw new PlcRuntimeException("Unsupported type: " +
obj.getClass().getName());
- }
-
- DateAndTime dt = msg.getTimeStamp();
- int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 :
dt.getYear() + 2000;
- LocalDateTime ldt = LocalDateTime.of(year,
- dt.getMonth(),
- dt.getDay(),
- dt.getHour(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMsec() * 1000000);
- this.timeStamp = ldt.toInstant(ZoneOffset.UTC);
- map.put(S7SysEvent.Fields.TIMESTAMP.name(), this.timeStamp);
-
- List<AlarmMessageObjectPushType> items = msg.getMessageObjects();
- for (AlarmMessageObjectPushType item : items) {
- map.put(Fields.EVENT_ID.name(), item.getEventId());
-
- if (obj instanceof S7PayloadAlarm8)
- map.put(Fields.TYPE.name(), "ALARM8");
- if (obj instanceof S7PayloadNotify)
- map.put(Fields.TYPE.name(), "NOTIFY");
- if (obj instanceof S7PayloadAlarmSQ)
- map.put(Fields.TYPE.name(), "ALARMSQ");
- if (obj instanceof S7PayloadAlarmS)
- map.put(Fields.TYPE.name(), "ALARMS");
- if (obj instanceof S7PayloadNotify8)
- map.put(Fields.TYPE.name(), "NOTIFY8");
-
-
- map.put(Fields.ASSOCIATED_VALUES.name(),
item.getNumberOfValues());
-
-
- map.put(Fields.SIG_1.name(), item.getEventState().getSIG_1());
- map.put(Fields.SIG_2.name(), item.getEventState().getSIG_2());
- map.put(Fields.SIG_3.name(), item.getEventState().getSIG_3());
- map.put(Fields.SIG_4.name(), item.getEventState().getSIG_4());
- map.put(Fields.SIG_5.name(), item.getEventState().getSIG_5());
- map.put(Fields.SIG_6.name(), item.getEventState().getSIG_6());
- map.put(Fields.SIG_7.name(), item.getEventState().getSIG_7());
- map.put(Fields.SIG_8.name(), item.getEventState().getSIG_8());
-
-
- map.put(Fields.SIG_1_STATE.name(),
item.getLocalState().getSIG_1());
- map.put(Fields.SIG_2_STATE.name(),
item.getLocalState().getSIG_2());
- map.put(Fields.SIG_3_STATE.name(),
item.getLocalState().getSIG_3());
- map.put(Fields.SIG_4_STATE.name(),
item.getLocalState().getSIG_4());
- map.put(Fields.SIG_5_STATE.name(),
item.getLocalState().getSIG_5());
- map.put(Fields.SIG_6_STATE.name(),
item.getLocalState().getSIG_6());
- map.put(Fields.SIG_7_STATE.name(),
item.getLocalState().getSIG_7());
- map.put(Fields.SIG_8_STATE.name(),
item.getLocalState().getSIG_8());
-
- map.put(Fields.SIG_1_DATA_GOING.name(),
item.getAckStateGoing().getSIG_1());
- map.put(Fields.SIG_2_DATA_GOING.name(),
item.getAckStateGoing().getSIG_2());
- map.put(Fields.SIG_3_DATA_GOING.name(),
item.getAckStateGoing().getSIG_3());
- map.put(Fields.SIG_4_DATA_GOING.name(),
item.getAckStateGoing().getSIG_4());
- map.put(Fields.SIG_5_DATA_GOING.name(),
item.getAckStateGoing().getSIG_5());
- map.put(Fields.SIG_6_DATA_GOING.name(),
item.getAckStateGoing().getSIG_6());
- map.put(Fields.SIG_7_DATA_GOING.name(),
item.getAckStateGoing().getSIG_7());
- map.put(Fields.SIG_8_DATA_GOING.name(),
item.getAckStateGoing().getSIG_8());
-
- map.put(Fields.SIG_1_DATA_COMING.name(),
item.getAckStateComing().getSIG_1());
- map.put(Fields.SIG_2_DATA_COMING.name(),
item.getAckStateComing().getSIG_2());
- map.put(Fields.SIG_3_DATA_COMING.name(),
item.getAckStateComing().getSIG_3());
- map.put(Fields.SIG_4_DATA_COMING.name(),
item.getAckStateComing().getSIG_4());
- map.put(Fields.SIG_5_DATA_COMING.name(),
item.getAckStateComing().getSIG_5());
- map.put(Fields.SIG_6_DATA_COMING.name(),
item.getAckStateComing().getSIG_6());
- map.put(Fields.SIG_7_DATA_COMING.name(),
item.getAckStateComing().getSIG_7());
- map.put(Fields.SIG_8_DATA_COMING.name(),
item.getAckStateComing().getSIG_8());
-
- List<AssociatedValueType> values = item.getAssociatedValues();
- int i = 1;
- int j = 0;
- for (AssociatedValueType value : values) {
- map.put("SIG_" + i + "_DATA_STATUS",
value.getReturnCode().getValue());
- map.put("SIG_" + i + "_DATA_SIZE",
value.getTransportSize().getValue());
- map.put("SIG_" + i + "_DATA_LENGTH",
value.getValueLength());
- byte[] data = new byte[value.getData().size()];
- j = 0;
- for (short s : value.getData()) {
- data[j] = (byte) s;
- j++;
- }
- map.put("SIG_" + i + "_DATA", data);
- i++;
- }
-
- }
-
- }
-
+ S7AlarmEvent(Instant timestamp, Map<String, Object> obj) {
+ super(timestamp);
+ this.map = obj;
}
- ;
-
@Override
public Map<String, Object> getMap() {
return map;
}
- @Override
- public Instant getTimestamp() {
- return timeStamp;
- }
-
@Override
public PlcReadRequest getRequest() {
throw new UnsupportedOperationException("Not supported yet.");
@@ -671,5 +519,160 @@ public class S7AlarmEvent implements S7Event {
throw new UnsupportedOperationException("Not supported yet.");
}
+ public static S7AlarmEvent of(Object obj) {
+ if (obj instanceof S7PayloadAlarmAckInd) {
+ AlarmMessageAckPushType msg = ((S7PayloadAlarmAckInd)
obj).getAlarmMessage();
+ DateAndTime dt = msg.getTimeStamp();
+ int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 :
dt.getYear() + 2000;
+ LocalDateTime ldt = LocalDateTime.of(year,
+ dt.getMonth(),
+ dt.getDay(),
+ dt.getHour(),
+ dt.getMinutes(),
+ dt.getSeconds(),
+ dt.getMsec() * 1000000);
+ Instant timeStamp = ldt.toInstant(ZoneOffset.UTC);
+
+ Map<String, Object> map = new HashMap<>();
+ map.put(S7SysEvent.Fields.TIMESTAMP.name(), timeStamp);
+
+ List<AlarmMessageAckObjectPushType> items =
msg.getMessageObjects();
+ for (AlarmMessageAckObjectPushType item : items) {
+ map.put(Fields.EVENT_ID.name(), item.getEventId());
+ map.put(Fields.TYPE.name(), "ALARMACK_IND");
+ map.put(Fields.ASSOCIATED_VALUES.name(),
item.getNumberOfValues());
+
+ map.put(Fields.SIG_1_DATA_GOING.name(),
item.getAckStateGoing().getSIG_1());
+ map.put(Fields.SIG_2_DATA_GOING.name(),
item.getAckStateGoing().getSIG_2());
+ map.put(Fields.SIG_3_DATA_GOING.name(),
item.getAckStateGoing().getSIG_3());
+ map.put(Fields.SIG_4_DATA_GOING.name(),
item.getAckStateGoing().getSIG_4());
+ map.put(Fields.SIG_5_DATA_GOING.name(),
item.getAckStateGoing().getSIG_5());
+ map.put(Fields.SIG_6_DATA_GOING.name(),
item.getAckStateGoing().getSIG_6());
+ map.put(Fields.SIG_7_DATA_GOING.name(),
item.getAckStateGoing().getSIG_7());
+ map.put(Fields.SIG_8_DATA_GOING.name(),
item.getAckStateGoing().getSIG_8());
+
+ map.put(Fields.SIG_1_DATA_COMING.name(),
item.getAckStateComing().getSIG_1());
+ map.put(Fields.SIG_2_DATA_COMING.name(),
item.getAckStateComing().getSIG_2());
+ map.put(Fields.SIG_3_DATA_COMING.name(),
item.getAckStateComing().getSIG_3());
+ map.put(Fields.SIG_4_DATA_COMING.name(),
item.getAckStateComing().getSIG_4());
+ map.put(Fields.SIG_5_DATA_COMING.name(),
item.getAckStateComing().getSIG_5());
+ map.put(Fields.SIG_6_DATA_COMING.name(),
item.getAckStateComing().getSIG_6());
+ map.put(Fields.SIG_7_DATA_COMING.name(),
item.getAckStateComing().getSIG_7());
+ map.put(Fields.SIG_8_DATA_COMING.name(),
item.getAckStateComing().getSIG_8());
+ }
+ return new S7AlarmEvent(timeStamp, map);
+ } else {
+
+ AlarmMessagePushType msg = null;
+
+ if (obj instanceof S7PayloadAlarm8) {
+ msg = ((S7PayloadAlarm8) obj).getAlarmMessage();
+ } else if (obj instanceof S7PayloadNotify) {
+ msg = ((S7PayloadNotify) obj).getAlarmMessage();
+ } else if (obj instanceof S7PayloadAlarmSQ) {
+ msg = ((S7PayloadAlarmSQ) obj).getAlarmMessage();
+ } else if (obj instanceof S7PayloadAlarmS) {
+ msg = ((S7PayloadAlarmS) obj).getAlarmMessage();
+ } else if (obj instanceof S7PayloadNotify8) {
+ msg = ((S7PayloadNotify8) obj).getAlarmMessage();
+ } else {
+ throw new PlcRuntimeException("Unsupported type: " +
obj.getClass().getName());
+ }
+
+ DateAndTime dt = msg.getTimeStamp();
+ int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 :
dt.getYear() + 2000;
+ LocalDateTime ldt = LocalDateTime.of(year,
+ dt.getMonth(),
+ dt.getDay(),
+ dt.getHour(),
+ dt.getMinutes(),
+ dt.getSeconds(),
+ dt.getMsec() * 1000000);
+ Instant timeStamp = ldt.toInstant(ZoneOffset.UTC);
+
+ Map<String, Object> map = new HashMap<>();
+ map.put(S7SysEvent.Fields.TIMESTAMP.name(), timeStamp);
+
+ List<AlarmMessageObjectPushType> items = msg.getMessageObjects();
+ for (AlarmMessageObjectPushType item : items) {
+ map.put(Fields.EVENT_ID.name(), item.getEventId());
+
+ if (obj instanceof S7PayloadAlarm8) {
+ map.put(Fields.TYPE.name(), "ALARM8");
+ }
+ if (obj instanceof S7PayloadNotify) {
+ map.put(Fields.TYPE.name(), "NOTIFY");
+ }
+ if (obj instanceof S7PayloadAlarmSQ) {
+ map.put(Fields.TYPE.name(), "ALARMSQ");
+ }
+ if (obj instanceof S7PayloadAlarmS) {
+ map.put(Fields.TYPE.name(), "ALARMS");
+ }
+ if (obj instanceof S7PayloadNotify8) {
+ map.put(Fields.TYPE.name(), "NOTIFY8");
+ }
+
+ map.put(Fields.ASSOCIATED_VALUES.name(),
item.getNumberOfValues());
+
+ map.put(Fields.SIG_1.name(), item.getEventState().getSIG_1());
+ map.put(Fields.SIG_2.name(), item.getEventState().getSIG_2());
+ map.put(Fields.SIG_3.name(), item.getEventState().getSIG_3());
+ map.put(Fields.SIG_4.name(), item.getEventState().getSIG_4());
+ map.put(Fields.SIG_5.name(), item.getEventState().getSIG_5());
+ map.put(Fields.SIG_6.name(), item.getEventState().getSIG_6());
+ map.put(Fields.SIG_7.name(), item.getEventState().getSIG_7());
+ map.put(Fields.SIG_8.name(), item.getEventState().getSIG_8());
+
+
+ map.put(Fields.SIG_1_STATE.name(),
item.getLocalState().getSIG_1());
+ map.put(Fields.SIG_2_STATE.name(),
item.getLocalState().getSIG_2());
+ map.put(Fields.SIG_3_STATE.name(),
item.getLocalState().getSIG_3());
+ map.put(Fields.SIG_4_STATE.name(),
item.getLocalState().getSIG_4());
+ map.put(Fields.SIG_5_STATE.name(),
item.getLocalState().getSIG_5());
+ map.put(Fields.SIG_6_STATE.name(),
item.getLocalState().getSIG_6());
+ map.put(Fields.SIG_7_STATE.name(),
item.getLocalState().getSIG_7());
+ map.put(Fields.SIG_8_STATE.name(),
item.getLocalState().getSIG_8());
+
+ map.put(Fields.SIG_1_DATA_GOING.name(),
item.getAckStateGoing().getSIG_1());
+ map.put(Fields.SIG_2_DATA_GOING.name(),
item.getAckStateGoing().getSIG_2());
+ map.put(Fields.SIG_3_DATA_GOING.name(),
item.getAckStateGoing().getSIG_3());
+ map.put(Fields.SIG_4_DATA_GOING.name(),
item.getAckStateGoing().getSIG_4());
+ map.put(Fields.SIG_5_DATA_GOING.name(),
item.getAckStateGoing().getSIG_5());
+ map.put(Fields.SIG_6_DATA_GOING.name(),
item.getAckStateGoing().getSIG_6());
+ map.put(Fields.SIG_7_DATA_GOING.name(),
item.getAckStateGoing().getSIG_7());
+ map.put(Fields.SIG_8_DATA_GOING.name(),
item.getAckStateGoing().getSIG_8());
+
+ map.put(Fields.SIG_1_DATA_COMING.name(),
item.getAckStateComing().getSIG_1());
+ map.put(Fields.SIG_2_DATA_COMING.name(),
item.getAckStateComing().getSIG_2());
+ map.put(Fields.SIG_3_DATA_COMING.name(),
item.getAckStateComing().getSIG_3());
+ map.put(Fields.SIG_4_DATA_COMING.name(),
item.getAckStateComing().getSIG_4());
+ map.put(Fields.SIG_5_DATA_COMING.name(),
item.getAckStateComing().getSIG_5());
+ map.put(Fields.SIG_6_DATA_COMING.name(),
item.getAckStateComing().getSIG_6());
+ map.put(Fields.SIG_7_DATA_COMING.name(),
item.getAckStateComing().getSIG_7());
+ map.put(Fields.SIG_8_DATA_COMING.name(),
item.getAckStateComing().getSIG_8());
+
+ List<AssociatedValueType> values = item.getAssociatedValues();
+ int i = 1;
+ int j = 0;
+ for (AssociatedValueType value : values) {
+ map.put("SIG_" + i + "_DATA_STATUS",
value.getReturnCode().getValue());
+ map.put("SIG_" + i + "_DATA_SIZE",
value.getTransportSize().getValue());
+ map.put("SIG_" + i + "_DATA_LENGTH",
value.getValueLength());
+ byte[] data = new byte[value.getData().size()];
+ j = 0;
+ for (short s : value.getData()) {
+ data[j] = (byte) s;
+ j++;
+ }
+ map.put("SIG_" + i + "_DATA", data);
+ i++;
+ }
+ }
+
+ return new S7AlarmEvent(timeStamp, map);
+ }
+
+ }
}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7CyclicEvent.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7CyclicEvent.java
index 6b7c83d3bf..9c0d0fcd83 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7CyclicEvent.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7CyclicEvent.java
@@ -17,12 +17,14 @@
* under the License.
*/
package org.apache.plc4x.java.s7.events;
-
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import static io.netty.buffer.Unpooled.wrappedBuffer;
+import org.apache.plc4x.java.api.messages.PlcMetadataKeys;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.value.PlcValue;
@@ -31,7 +33,6 @@ import
org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItemCyclicServicesCha
import
org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItemCyclicServicesPush;
import
org.apache.plc4x.java.s7.readwrite.S7PayloadUserDataItemCyclicServicesSubscribeResponse;
import org.apache.plc4x.java.s7.readwrite.utils.StaticHelper;
-
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
@@ -43,8 +44,7 @@ import
org.apache.plc4x.java.s7.readwrite.tag.S7SubscriptionTag;
import org.apache.plc4x.java.s7.readwrite.tag.S7Tag;
import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionTag;
import org.apache.plc4x.java.spi.values.DefaultPlcValueHandler;
-
-public class S7CyclicEvent implements S7Event {
+public class S7CyclicEvent extends S7EventBase implements S7Event {
public enum Fields {
TYPE,
@@ -57,110 +57,104 @@ public class S7CyclicEvent implements S7Event {
TRANSPORTSIZE_,
DATA_
}
-
private final PlcSubscriptionRequest request;
-
- private final Instant timeStamp;
private final Map<String, Object> map;
-
public S7CyclicEvent(PlcSubscriptionRequest request, short jobid,
S7PayloadUserDataItemCyclicServicesPush event) {
+ super(Instant.now(), new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.ASSUMPTION)
+ .build()
+ );
this.map = new HashMap<>();
- this.timeStamp = Instant.now();
this.request = request;
map.put(Fields.TYPE.name(), "CYCEVENT");
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ map.put(Fields.TIMESTAMP.name(), getTimestamp());
map.put(Fields.JOBID.name(), jobid);
map.put(Fields.ITEMSCOUNT.name(), event.getItemsCount());
- int[] n = new int[1];
+ int[] n = new int[1];
request.getTagNames().forEach(tagname -> {
int i = n[0];
map.put(Fields.RETURNCODE_.name() + i,
event.getItems().get(i).getReturnCode().getValue());
map.put(Fields.TRANSPORTSIZE_.name() + i,
event.getItems().get(i).getTransportSize().getValue());
map.put(tagname, dataToPlcValue(tagname, request,
event.getItems().get(i).getData()));
- n[0]++;
+ n[0]++;
});
-
}
-
public S7CyclicEvent(PlcSubscriptionRequest request, short jobid,
S7PayloadUserDataItemCyclicServicesChangeDrivenPush event) {
+ super(Instant.now(), new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.ASSUMPTION)
+ .build()
+ );
this.map = new HashMap<>();
- this.timeStamp = Instant.now();
this.request = request;
map.put(Fields.TYPE.name(), "CYCEVENT");
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ map.put(Fields.TIMESTAMP.name(), getTimestamp());
map.put(Fields.JOBID.name(), jobid);
map.put(Fields.ITEMSCOUNT.name(), event.getItemsCount());
int[] n = new int[1];
-
+
request.getTagNames().forEach(tagname -> {
int i = n[0];
map.put(Fields.RETURNCODE_.name() + i,
event.getItems().get(i).getReturnCode().getValue());
map.put(Fields.TRANSPORTSIZE_.name() + i,
event.getItems().get(i).getTransportSize().getValue());
map.put(tagname, dataToPlcValue(tagname, request,
event.getItems().get(i).getData()));
- n[0]++;
+ n[0]++;
});
-
+
}
-
public S7CyclicEvent(PlcSubscriptionRequest request, short jobid,
S7PayloadUserDataItemCyclicServicesSubscribeResponse event) {
+ super(Instant.now(), new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.ASSUMPTION)
+ .build()
+ );
this.map = new HashMap<>();
- this.timeStamp = Instant.now();
this.request = request;
map.put(Fields.TYPE.name(), "CYCEVENT");
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ map.put(Fields.TIMESTAMP.name(), getTimestamp());
map.put(Fields.JOBID.name(), jobid);
map.put(Fields.ITEMSCOUNT.name(), event.getItemsCount());
int[] n = new int[1];
-
request.getTagNames().forEach(tagname -> {
int i = n[0];
map.put(Fields.RETURNCODE_.name() + i,
event.getItems().get(i).getReturnCode().getValue());
map.put(Fields.TRANSPORTSIZE_.name() + i,
event.getItems().get(i).getTransportSize().getValue());
map.put(tagname, dataToPlcValue(tagname, request,
event.getItems().get(i).getData()));
- n[0]++;
- });
+ n[0]++;
+ });
}
-
public S7CyclicEvent(PlcSubscriptionRequest request, short jobid,
S7PayloadUserDataItemCyclicServicesChangeDrivenSubscribeResponse event) {
+ super(Instant.now(), new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.ASSUMPTION)
+ .build()
+ );
this.map = new HashMap<>();
- this.timeStamp = Instant.now();
this.request = request;
map.put(Fields.TYPE.name(), "CYCEVENT");
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ map.put(Fields.TIMESTAMP.name(), getTimestamp());
map.put(Fields.JOBID.name(), jobid);
map.put(Fields.ITEMSCOUNT.name(), event.getItemsCount());
int[] n = new int[1];
-
+
request.getTagNames().forEach(tagname -> {
int i = n[0];
map.put(Fields.RETURNCODE_.name() + i,
event.getItems().get(i).getReturnCode().getValue());
map.put(Fields.TRANSPORTSIZE_.name() + i,
event.getItems().get(i).getTransportSize().getValue());
map.put(tagname, dataToPlcValue(tagname, request,
event.getItems().get(i).getData()));
- n[0]++;
- });
+ n[0]++;
+ });
}
-
@Override
public Map<String, Object> getMap() {
return this.map;
}
-
- @Override
- public Instant getTimestamp() {
- return this.timeStamp;
- }
-
@Override
public PlcReadRequest getRequest() {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public PlcValue getAsPlcValue() {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public PlcValue getPlcValue(String name) {
if (request.getTagNames().contains(name)) {
@@ -170,33 +164,27 @@ public class S7CyclicEvent implements S7Event {
}
return null;
}
-
@Override
public int getNumberOfValues(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public Object getObject(String name) {
if ("REQUEST".equals(name)) return request;
return null;
}
-
@Override
public Object getObject(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public Collection<Object> getAllObjects(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidBoolean(String name) {
return isValidBoolean(name, 0);
}
-
@Override
public boolean isValidBoolean(String name, int index) {
try {
@@ -206,12 +194,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Boolean getBoolean(String name) {
return getBoolean(name, 0);
}
-
@Override
public Boolean getBoolean(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -220,17 +206,14 @@ public class S7CyclicEvent implements S7Event {
ByteBuf byteBuf = Unpooled.wrappedBuffer((byte[]) map.get(name));
return byteBuf.getBoolean(index);
}
-
@Override
public Collection<Boolean> getAllBooleans(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidByte(String name) {
return isValidByte(name, 0);
}
-
@Override
public boolean isValidByte(String name, int index) {
try {
@@ -240,12 +223,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Byte getByte(String name) {
return getByte(name, 0);
}
-
@Override
public Byte getByte(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -255,7 +236,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Byte.BYTES;
return byteBuf.getByte(pos);
}
-
@Override
public Collection<Byte> getAllBytes(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -264,12 +244,10 @@ public class S7CyclicEvent implements S7Event {
byte[] array = (byte[]) map.get(name);
return IntStream.range(0, array.length).mapToObj(i ->
array[i]).collect(Collectors.toList());
}
-
@Override
public boolean isValidShort(String name) {
return isValidShort(name, 0);
}
-
@Override
public boolean isValidShort(String name, int index) {
try {
@@ -279,12 +257,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Short getShort(String name) {
return getShort(name, 0);
}
-
@Override
public Short getShort(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -294,7 +270,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Short.BYTES;
return byteBuf.getShort(pos);
}
-
@Override
public Collection<Short> getAllShorts(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -307,12 +282,10 @@ public class S7CyclicEvent implements S7Event {
}
return list;
}
-
@Override
public boolean isValidInteger(String name) {
return isValidInteger(name, 0);
}
-
@Override
public boolean isValidInteger(String name, int index) {
try {
@@ -322,12 +295,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Integer getInteger(String name) {
return getInteger(name, 0);
}
-
@Override
public Integer getInteger(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -337,7 +308,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Integer.BYTES;
return byteBuf.getInt(pos);
}
-
@Override
public Collection<Integer> getAllIntegers(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -350,37 +320,30 @@ public class S7CyclicEvent implements S7Event {
}
return list;
}
-
@Override
public boolean isValidBigInteger(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidBigInteger(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public BigInteger getBigInteger(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public BigInteger getBigInteger(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public Collection<BigInteger> getAllBigIntegers(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidLong(String name) {
return isValidLong(name, 0);
}
-
@Override
public boolean isValidLong(String name, int index) {
try {
@@ -390,12 +353,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Long getLong(String name) {
return getLong(name, 0);
}
-
@Override
public Long getLong(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -405,7 +366,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Long.BYTES;
return byteBuf.getLong(pos);
}
-
@Override
public Collection<Long> getAllLongs(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -418,12 +378,10 @@ public class S7CyclicEvent implements S7Event {
}
return list;
}
-
@Override
public boolean isValidFloat(String name) {
return isValidFloat(name, 0);
}
-
@Override
public boolean isValidFloat(String name, int index) {
try {
@@ -433,12 +391,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Float getFloat(String name) {
return getFloat(name, 0);
}
-
@Override
public Float getFloat(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -448,7 +404,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Float.BYTES;
return byteBuf.getFloat(pos);
}
-
@Override
public Collection<Float> getAllFloats(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -461,12 +416,10 @@ public class S7CyclicEvent implements S7Event {
}
return list;
}
-
@Override
public boolean isValidDouble(String name) {
return isValidDouble(name, 0);
}
-
@Override
public boolean isValidDouble(String name, int index) {
try {
@@ -476,12 +429,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public Double getDouble(String name) {
return getDouble(name, 0);
}
-
@Override
public Double getDouble(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -491,7 +442,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Double.BYTES;
return byteBuf.getDouble(pos);
}
-
@Override
public Collection<Double> getAllDoubles(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -504,37 +454,30 @@ public class S7CyclicEvent implements S7Event {
}
return list;
}
-
@Override
public boolean isValidBigDecimal(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidBigDecimal(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public BigDecimal getBigDecimal(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public BigDecimal getBigDecimal(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public Collection<BigDecimal> getAllBigDecimals(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidString(String name) {
return isValidString(name, 0);
}
-
@Override
public boolean isValidString(String name, int index) {
try {
@@ -544,7 +487,6 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public String getString(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -553,22 +495,18 @@ public class S7CyclicEvent implements S7Event {
ByteBuf byteBuf = Unpooled.wrappedBuffer((byte[]) map.get(name));
return byteBuf.toString(Charset.defaultCharset());
}
-
@Override
public String getString(String name, int index) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public Collection<String> getAllStrings(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean isValidTime(String name) {
return isValidTime(name, 0);
}
-
@Override
public boolean isValidTime(String name, int index) {
try {
@@ -578,12 +516,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public LocalTime getTime(String name) {
return getTime(name, 0);
}
-
/*
* In S7, data type TIME occupies one double word.
* The value is in milliseconds (ms).
@@ -599,7 +535,6 @@ public class S7CyclicEvent implements S7Event {
Duration dr = StaticHelper.s7TimeToDuration(value);
return LocalTime.of(dr.toHoursPart(), dr.toMinutesPart(),
dr.toSecondsPart(), dr.toNanosPart());
}
-
@Override
public Collection<LocalTime> getAllTimes(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -613,12 +548,10 @@ public class S7CyclicEvent implements S7Event {
}
return items;
}
-
@Override
public boolean isValidDate(String name) {
return isValidDate(name, 0);
}
-
@Override
public boolean isValidDate(String name, int index) {
try {
@@ -628,12 +561,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public LocalDate getDate(String name) {
return getDate(name, 0);
}
-
@Override
public LocalDate getDate(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -644,7 +575,6 @@ public class S7CyclicEvent implements S7Event {
short value = byteBuf.getShort(pos);
return StaticHelper.s7DateToLocalDate(value);
}
-
@Override
public Collection<LocalDate> getAllDates(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -658,12 +588,10 @@ public class S7CyclicEvent implements S7Event {
}
return items;
}
-
@Override
public boolean isValidDateTime(String name) {
return isValidDateTime(name, 0);
}
-
@Override
public boolean isValidDateTime(String name, int index) {
try {
@@ -673,12 +601,10 @@ public class S7CyclicEvent implements S7Event {
return false;
}
}
-
@Override
public LocalDateTime getDateTime(String name) {
return getDateTime(name, 0);
}
-
@Override
public LocalDateTime getDateTime(String name, int index) {
if (!(map.get(name) instanceof byte[])) {
@@ -688,7 +614,6 @@ public class S7CyclicEvent implements S7Event {
int pos = index * Long.BYTES;
return StaticHelper.s7DateTimeToLocalDateTime(byteBuf.slice(pos,
Long.BYTES));
}
-
@Override
public Collection<LocalDateTime> getAllDateTimes(String name) {
if (!(map.get(name) instanceof byte[])) {
@@ -702,22 +627,18 @@ public class S7CyclicEvent implements S7Event {
}
return items;
}
-
@Override
public Collection<String> getTagNames() {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public PlcTag getTag(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public PlcResponseCode getResponseCode(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
-
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -730,7 +651,7 @@ public class S7CyclicEvent implements S7Event {
return false;
}
final S7CyclicEvent other = (S7CyclicEvent) obj;
-
+
for (String tag:request.getTagNames()) {
final PlcValue othervalue = other.getPlcValue(tag);
if (othervalue == null) return false;
@@ -739,7 +660,7 @@ public class S7CyclicEvent implements S7Event {
return false;
}
};
-
+
return true;
}
@@ -747,23 +668,22 @@ public class S7CyclicEvent implements S7Event {
private static PlcValue dataToPlcValue(String tagname,
PlcSubscriptionRequest request, List<Short> data){
int[] i = new int[1];
-
+
final byte[] buffer = new byte[data.size()];
-
data.forEach( b -> {
- buffer[i[0]] = b.byteValue();
+ buffer[i[0]] = b.byteValue();
i[0]++;
});
-
+
ByteBuf bb = wrappedBuffer(buffer);
-
-
+
+
final DefaultPlcSubscriptionTag dpst = (DefaultPlcSubscriptionTag)
request.getTag(tagname);
final S7SubscriptionTag subTag = (S7SubscriptionTag) dpst.getTag();
final S7Tag[] s7Tags = subTag.getS7Tags();
-
+
PlcValue plcValue = null;
-
+
switch(s7Tags[0].getDataType()){
case BOOL:
Boolean[] bools = new Boolean[s7Tags[0].getNumberOfElements()];
@@ -781,11 +701,11 @@ public class S7CyclicEvent implements S7Event {
plcValue = DefaultPlcValueHandler.of(s7Tags[0], bytes);
break;
case WORD:
- break;
+ break;
case DWORD:
- break;
+ break;
case LWORD:
- break;
+ break;
case INT:
Short[] shorts = new Short[s7Tags[0].getNumberOfElements()];
for (int iter = 0; iter < s7Tags[0].getNumberOfElements();
iter ++) {
@@ -794,11 +714,11 @@ public class S7CyclicEvent implements S7Event {
plcValue = DefaultPlcValueHandler.of(s7Tags[0], shorts);
break;
case UINT:
- break;
+ break;
case SINT:
- break;
+ break;
case USINT:
- break;
+ break;
case DINT:
// TODO: This looks suspicious
Integer[] integers = new Integer[bb.capacity() / Integer.SIZE];
@@ -808,7 +728,7 @@ public class S7CyclicEvent implements S7Event {
plcValue = DefaultPlcValueHandler.of(s7Tags[0], integers);
break;
case UDINT:
- break;
+ break;
case LINT:
// TODO: This looks suspicious
Long[] longs = new Long[bb.capacity() / Long.SIZE];
@@ -818,7 +738,7 @@ public class S7CyclicEvent implements S7Event {
plcValue = DefaultPlcValueHandler.of(s7Tags[0], longs);
break;
case ULINT:
- break;
+ break;
case REAL:
// TODO: This looks suspicious
Float[] floats = new Float[bb.capacity() / Float.SIZE];
@@ -836,42 +756,41 @@ public class S7CyclicEvent implements S7Event {
plcValue = DefaultPlcValueHandler.of(s7Tags[0], doubles);
break;
case CHAR:
- break;
+ break;
case WCHAR:
- break;
+ break;
case STRING:
- break;
+ break;
case WSTRING:
- break;
+ break;
case S5TIME:
break;
case TIME:
- break;
+ break;
case LTIME:
- break;
+ break;
case DATE:
- break;
+ break;
case TIME_OF_DAY:
- break;
+ break;
case TOD:
- break;
+ break;
case LTIME_OF_DAY:
- break;
+ break;
case LTOD:
- break;
+ break;
case DATE_AND_TIME:
- break;
+ break;
case DT:
- break;
+ break;
case DATE_AND_LTIME:
- break;
+ break;
case LDT:
- break;
+ break;
case DTL:
- break;
+ break;
}
-
+
return plcValue;
}
-
}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7EventBase.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7EventBase.java
new file mode 100644
index 0000000000..7580b72923
--- /dev/null
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7EventBase.java
@@ -0,0 +1,59 @@
+/*
+ * 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.s7.events;
+
+import java.time.Instant;
+import org.apache.plc4x.java.api.messages.PlcMetadataKeys;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
+
+public abstract class S7EventBase implements S7Event {
+
+ private final Instant timestamp;
+ private final Metadata metadata;
+
+ S7EventBase() {
+ this(Instant.now());
+ }
+
+ S7EventBase(Instant timestamp) {
+ this(timestamp, new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP, timestamp.getEpochSecond())
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.HARDWARE) //
event triggered by PLC itself
+ .build()
+ );
+ }
+
+ S7EventBase(Instant timestamp, Metadata metadata) {
+ this.timestamp = timestamp;
+ this.metadata = metadata;
+ }
+
+ @Override
+ public Metadata getTagMetadata(String name) {
+ return metadata;
+ }
+
+ @Override
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7ModeEvent.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7ModeEvent.java
index b144ad8510..4a9d53f204 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7ModeEvent.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7ModeEvent.java
@@ -18,7 +18,10 @@
*/
package org.apache.plc4x.java.s7.events;
+import org.apache.plc4x.java.api.messages.PlcMetadataKeys;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
+import org.apache.plc4x.java.api.metadata.time.TimeSource;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.value.PlcValue;
@@ -34,7 +37,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-public class S7ModeEvent implements S7Event {
+public class S7ModeEvent extends S7EventBase implements S7Event {
public enum Fields {
@@ -46,17 +49,19 @@ public class S7ModeEvent implements S7Event {
CURRENT_MODE
}
- private final Instant timeStamp;
private final Map<String, Object> map;
public S7ModeEvent(S7ParameterModeTransition parameter) {
+ super(Instant.now(), new DefaultMetadata.Builder()
+ .put(PlcMetadataKeys.TIMESTAMP_SOURCE, TimeSource.ASSUMPTION)
+ .build()
+ );
this.map = new HashMap<>();
map.put(Fields.TYPE.name(), "MODE");
map.put(Fields.METHOD.name(), parameter.getMethod());
map.put(Fields.FUNCTION.name(), parameter.getCpuFunctionType());
map.put(Fields.CURRENT_MODE.name(), parameter.getCurrentMode());
- this.timeStamp = Instant.now();
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ map.put(Fields.TIMESTAMP.name(), getTimestamp());
// TODO: Is this really correct, to put the map itself in itself?
map.put(Fields.MAP.name(), map);
}
@@ -67,11 +72,6 @@ public class S7ModeEvent implements S7Event {
return map;
}
- @Override
- public Instant getTimestamp() {
- return timeStamp;
- }
-
@Override
public PlcReadRequest getRequest() {
throw new UnsupportedOperationException("Not supported yet.");
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7SysEvent.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7SysEvent.java
index 4fab9b97ac..364adc443d 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7SysEvent.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7SysEvent.java
@@ -32,7 +32,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-public class S7SysEvent implements S7Event {
+public class S7SysEvent extends S7EventBase implements S7Event {
public enum Fields {
TIMESTAMP,
@@ -45,30 +45,11 @@ public class S7SysEvent implements S7Event {
INFO2
}
- private final Instant timeStamp;
protected final Map<String, Object> map;
- public S7SysEvent(S7PayloadDiagnosticMessage payload) {
- this.map = new HashMap();
- map.put(Fields.TYPE.name(), "SYS");
- map.put(Fields.EVENT_ID.name(), payload.getEventId());
- map.put(Fields.PRIORITY_CLASS.name(), payload.getPriorityClass());
- map.put(Fields.OB_NUMBER.name(), payload.getObNumber());
- map.put(Fields.DAT_ID.name(), payload.getDatId());
- map.put(Fields.INFO1.name(), payload.getInfo1());
- map.put(Fields.INFO2.name(), payload.getInfo2());
-
- DateAndTime dt = payload.getTimeStamp();
- int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 : dt.getYear() +
2000;
- LocalDateTime ldt = LocalDateTime.of(year,
- dt.getMonth(),
- dt.getDay(),
- dt.getHour(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMsec() * 1000000);
- this.timeStamp = ldt.toInstant(ZoneOffset.UTC);
- map.put(Fields.TIMESTAMP.name(), this.timeStamp);
+ S7SysEvent(Instant instant, Map<String, Object> map) {
+ super(instant);
+ this.map = map;
}
@Override
@@ -76,11 +57,6 @@ public class S7SysEvent implements S7Event {
return map;
}
- @Override
- public Instant getTimestamp() {
- return timeStamp;
- }
-
@Override
public PlcReadRequest getRequest() {
throw new UnsupportedOperationException("Not supported yet.");
@@ -456,4 +432,27 @@ public class S7SysEvent implements S7Event {
throw new UnsupportedOperationException("Not supported yet.");
}
+ public static S7SysEvent of(S7PayloadDiagnosticMessage payload) {
+ Map<String, Object> map = new HashMap<>();
+ map.put(Fields.TYPE.name(), "SYS");
+ map.put(Fields.EVENT_ID.name(), payload.getEventId());
+ map.put(Fields.PRIORITY_CLASS.name(), payload.getPriorityClass());
+ map.put(Fields.OB_NUMBER.name(), payload.getObNumber());
+ map.put(Fields.DAT_ID.name(), payload.getDatId());
+ map.put(Fields.INFO1.name(), payload.getInfo1());
+ map.put(Fields.INFO2.name(), payload.getInfo2());
+
+ DateAndTime dt = payload.getTimeStamp();
+ int year = (dt.getYear() >= 90) ? dt.getYear() + 1900 : dt.getYear() +
2000;
+ LocalDateTime ldt = LocalDateTime.of(year,
+ dt.getMonth(),
+ dt.getDay(),
+ dt.getHour(),
+ dt.getMinutes(),
+ dt.getSeconds(),
+ dt.getMsec() * 1000000);
+ Instant timestamp = ldt.toInstant(ZoneOffset.UTC);
+ map.put(Fields.TIMESTAMP.name(), timestamp);
+ return new S7SysEvent(timestamp, map);
+ }
}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
index d9f01d0dbd..d5805fc25e 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
@@ -18,12 +18,21 @@
*/
package org.apache.plc4x.java.s7.events;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.plc4x.java.s7.readwrite.S7PayloadDiagnosticMessage;
public class S7UserEvent extends S7SysEvent {
- public S7UserEvent(S7PayloadDiagnosticMessage payload) {
- super(payload);
+ S7UserEvent(Instant instant, Map<String, Object> map) {
+ super(instant, map);
+ }
+
+ public static S7UserEvent of(S7PayloadDiagnosticMessage payload) {
+ S7SysEvent event = S7SysEvent.of(payload);
+ Map<String, Object> map = new HashMap<>(event.getMap());
map.put(Fields.TYPE.name(), "USER");
+ return new S7UserEvent(event.getTimestamp(), map);
}
}
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7ProtocolLogic.java
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7ProtocolLogic.java
index 8f4eee69c2..97aaa7835a 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7ProtocolLogic.java
+++
b/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7ProtocolLogic.java
@@ -1570,10 +1570,10 @@ public class S7ProtocolLogic extends
Plc4xProtocolBase<TPKTPacket> {
if (item instanceof S7PayloadDiagnosticMessage) {
final S7PayloadDiagnosticMessage pload =
(S7PayloadDiagnosticMessage) item;
if ((pload.getEventId() >= 0x0A000) &
(pload.getEventId() <= 0x0BFFF)) {
- S7UserEvent userEvent = new
S7UserEvent(pload);
+ S7UserEvent userEvent =
S7UserEvent.of(pload);
eventQueue.add(userEvent);
} else {
- S7SysEvent sysEvent = new
S7SysEvent(pload);
+ S7SysEvent sysEvent = S7SysEvent.of(pload);
eventQueue.add(sysEvent);
}
}
@@ -1589,7 +1589,7 @@ public class S7ProtocolLogic extends
Plc4xProtocolBase<TPKTPacket> {
(myParameter.getCpuSubfunction() == 0x16))) {
//(04)
payload.getItems().forEach(item ->{
- S7AlarmEvent alrmEvent = new S7AlarmEvent(item);
+ S7AlarmEvent alrmEvent = S7AlarmEvent.of(item);
eventQueue.add(alrmEvent);
});
diff --git
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcReadResponse.java
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcReadResponse.java
index 5983e519f6..f0ee410501 100644
---
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcReadResponse.java
+++
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcReadResponse.java
@@ -18,10 +18,13 @@
*/
package org.apache.plc4x.java.spi.messages;
+import java.util.Map.Entry;
import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.spi.generation.SerializationException;
@@ -46,11 +49,19 @@ public class DefaultPlcReadResponse implements
PlcReadResponse, Serializable {
private final PlcReadRequest request;
private final Map<String, PlcResponseItem<PlcValue>> values;
+ private final Map<String, Metadata> metadata;
public DefaultPlcReadResponse(PlcReadRequest request,
Map<String, PlcResponseItem<PlcValue>>
values) {
+ this(request, values, Collections.emptyMap());
+ }
+
+ public DefaultPlcReadResponse(PlcReadRequest request,
+ Map<String, PlcResponseItem<PlcValue>>
values,
+ Map<String, Metadata> metadata) {
this.request = request;
this.values = values;
+ this.metadata = Collections.unmodifiableMap(metadata);
}
@Override
@@ -58,6 +69,11 @@ public class DefaultPlcReadResponse implements
PlcReadResponse, Serializable {
return request;
}
+ @Override
+ public Metadata getTagMetadata(String tag) {
+ return metadata.getOrDefault(tag, DefaultMetadata.EMPTY);
+ }
+
@Override
public PlcValue getAsPlcValue() {
Map<String, PlcValue> structMap = new HashMap<>();
@@ -669,6 +685,20 @@ public class DefaultPlcReadResponse implements
PlcReadResponse, Serializable {
}
writeBuffer.popContext("values");
+ if (metadata != null && !metadata.isEmpty()) {
+ writeBuffer.pushContext("metadata", WithRenderAsList(true));
+
+ for (Entry<String, Metadata> entry : metadata.entrySet()) {
+ if (entry.getValue() instanceof Serializable) {
+ writeBuffer.pushContext(entry.getKey());
+ ((Serializable) entry.getValue()).serialize(writeBuffer);
+ writeBuffer.popContext(entry.getKey());
+ }
+ }
+
+ writeBuffer.popContext("metadata");
+ }
+
writeBuffer.popContext("PlcReadResponse");
}
diff --git
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcSubscriptionEvent.java
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcSubscriptionEvent.java
index 2035f76e00..e4ef1835aa 100644
---
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcSubscriptionEvent.java
+++
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcSubscriptionEvent.java
@@ -18,7 +18,10 @@
*/
package org.apache.plc4x.java.spi.messages;
+import java.util.Collections;
import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.metadata.DefaultMetadata;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.value.PlcValue;
import org.apache.plc4x.java.spi.messages.utils.PlcResponseItem;
@@ -32,8 +35,14 @@ public class DefaultPlcSubscriptionEvent extends
DefaultPlcReadResponse implemen
public final Instant timestamp;
public DefaultPlcSubscriptionEvent(Instant timestamp,
- Map<String, PlcResponseItem<PlcValue>>
tags) {
- super(null, tags);
+ Map<String, PlcResponseItem<PlcValue>> tags) {
+ this(timestamp, tags, Collections.emptyMap());
+ }
+
+ public DefaultPlcSubscriptionEvent(Instant timestamp,
+ Map<String, PlcResponseItem<PlcValue>>
tags,
+ Map<String, Metadata> metadata) {
+ super(null, tags, metadata);
this.timestamp = timestamp;
}
diff --git
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcWriteResponse.java
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcWriteResponse.java
index 329792956d..96ff7ad066 100644
---
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcWriteResponse.java
+++
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/messages/DefaultPlcWriteResponse.java
@@ -18,8 +18,11 @@
*/
package org.apache.plc4x.java.spi.messages;
+import java.util.Collections;
+import java.util.Map.Entry;
import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.api.metadata.Metadata;
import org.apache.plc4x.java.api.model.PlcTag;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.spi.generation.SerializationException;
@@ -36,11 +39,19 @@ public class DefaultPlcWriteResponse implements
PlcWriteResponse, Serializable {
private final PlcWriteRequest request;
private final Map<String, PlcResponseCode> responseCodes;
+ private final Map<String, Metadata> metadata;
public DefaultPlcWriteResponse(PlcWriteRequest request,
Map<String, PlcResponseCode> responseCodes)
{
+ this(request, responseCodes, Collections.emptyMap());
+ }
+
+ public DefaultPlcWriteResponse(PlcWriteRequest request,
+ Map<String, PlcResponseCode> responseCodes,
+ Map<String, Metadata> metadata) {
this.request = request;
this.responseCodes = responseCodes;
+ this.metadata = metadata;
}
@Override
@@ -48,6 +59,11 @@ public class DefaultPlcWriteResponse implements
PlcWriteResponse, Serializable {
return request;
}
+ @Override
+ public Metadata getTagMetadata(String tag) {
+ return metadata.getOrDefault(tag, Metadata.EMPTY);
+ }
+
@Override
public Collection<String> getTagNames() {
return request.getTagNames();
@@ -83,6 +99,20 @@ public class DefaultPlcWriteResponse implements
PlcWriteResponse, Serializable {
}
writeBuffer.popContext("responseCodes");
+ if (metadata != null && !metadata.isEmpty()) {
+ writeBuffer.pushContext("metadata", WithRenderAsList(true));
+
+ for (Entry<String, Metadata> entry : metadata.entrySet()) {
+ if (entry.getValue() instanceof Serializable) {
+ writeBuffer.pushContext(entry.getKey());
+ ((Serializable) entry.getValue()).serialize(writeBuffer);
+ writeBuffer.popContext(entry.getKey());
+ }
+ }
+
+ writeBuffer.popContext("metadata");
+ }
+
writeBuffer.popContext("PlcWriteResponse");
}
diff --git
a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/metadata/DefaultMetadata.java
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/metadata/DefaultMetadata.java
new file mode 100644
index 0000000000..55bd78b422
--- /dev/null
+++
b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/metadata/DefaultMetadata.java
@@ -0,0 +1,132 @@
+/*
+ * 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.spi.metadata;
+
+import static
org.apache.plc4x.java.spi.generation.WithReaderWriterArgs.WithRenderAsList;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.plc4x.java.api.metadata.Metadata;
+import org.apache.plc4x.java.spi.generation.SerializationException;
+import org.apache.plc4x.java.spi.generation.WriteBuffer;
+import org.apache.plc4x.java.spi.utils.Serializable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultMetadata implements Metadata, Serializable {
+
+ private final Metadata parent;
+ private final Map<Key<?>, Object> values;
+
+ DefaultMetadata(Map<Key<?>, Object> values) {
+ this(values, Metadata.EMPTY);
+ }
+
+ public DefaultMetadata(Map<Key<?>, Object> values, Metadata parent) {
+ this.values = new LinkedHashMap<>(values);
+ this.parent = Objects.requireNonNull(parent, "Parent metadata must not
be null");
+ }
+
+ @Override
+ public Set<Key<?>> keys() {
+ Set<Key<?>> keys = new LinkedHashSet<>(values.keySet());
+ keys.addAll(parent.keys());
+ return Collections.unmodifiableSet(keys);
+ }
+
+ @Override
+ public Map<Key<?>, Object> entries() {
+ Map<Key<?>, Object> copy = new LinkedHashMap<>(parent.entries());
+ copy.putAll(values);
+ return Map.copyOf(copy);
+ }
+
+ @Override
+ public Object getValue(Key<?> key) {
+ Object value = values.get(key);
+ if (value == null) {
+ return parent.getValue(key);
+ }
+ return value;
+ }
+
+ @Override
+ public void serialize(WriteBuffer writeBuffer) throws
SerializationException {
+ for (Key<?> metadataKey : keys()) {
+ writeBuffer.pushContext("entry", WithRenderAsList(false));
+ writeBuffer.writeString("key", metadataKey.getKey().length(),
metadataKey.getKey());
+ String value = "" + getValue(metadataKey);
+ writeBuffer.writeString("value", value.length(), value);
+ writeBuffer.popContext("entry");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Metadata)) {
+ return false;
+ }
+ Metadata that = (Metadata) o;
+ return Objects.equals(entries(), that.entries());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(entries());
+ }
+
+ public static class Builder {
+ private final Logger logger = LoggerFactory.getLogger(Builder.class);
+
+ private final Map<Key<?>, Object> values = new LinkedHashMap<>();
+ private final Metadata parent;
+
+ public Builder() {
+ this(DefaultMetadata.EMPTY);
+ }
+
+ public Builder(Metadata parent) {
+ this.parent = parent;
+ }
+
+ public <T> Builder put(Key<T> key, T value) {
+ if (!key.validate(value)) {
+ logger.debug("Ignore metadata value {}, it does not match
constraints imposed by key {}", value, key);
+ return this;
+ }
+
+ values.put(key, value);
+ return this;
+ }
+
+ public Metadata build() {
+ return new DefaultMetadata(values, parent);
+ }
+ }
+
+}
diff --git
a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/internal/validator/ApiValidator.java
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/internal/validator/ApiValidator.java
index ddf6439c34..1d538223bd 100644
---
a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/internal/validator/ApiValidator.java
+++
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/internal/validator/ApiValidator.java
@@ -19,6 +19,8 @@
package org.apache.plc4x.test.driver.internal.validator;
import org.apache.plc4x.test.driver.exceptions.DriverTestsuiteException;
+import org.apache.plc4x.test.driver.xmlunit.SkipAttributeFilter;
+import org.apache.plc4x.test.driver.xmlunit.SkipDifferenceEvaluator;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,12 +28,15 @@ import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.Diff;
public class ApiValidator {
+
private static final Logger LOGGER =
LoggerFactory.getLogger(ApiValidator.class);
public static void validateApiMessage(Element referenceXml, String
apiMessage) throws DriverTestsuiteException {
final String referenceXmlString = referenceXml.asXML();
final Diff diff = DiffBuilder.compare(referenceXmlString)
.withTest(apiMessage).checkForSimilar().ignoreComments().ignoreWhitespace()
+ .withDifferenceEvaluator(new SkipDifferenceEvaluator())
+ .withAttributeFilter(new SkipAttributeFilter())
.build();
if (diff.hasDifferences()) {
LOGGER.warn("got\n{}", apiMessage);
diff --git
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java
similarity index 60%
copy from
plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
copy to
plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java
index d9f01d0dbd..d7f63abff1 100644
---
a/plc4j/drivers/s7/src/main/java/org/apache/plc4x/java/s7/events/S7UserEvent.java
+++
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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
@@ -16,14 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.plc4x.java.s7.events;
-import org.apache.plc4x.java.s7.readwrite.S7PayloadDiagnosticMessage;
+package org.apache.plc4x.test.driver.xmlunit;
-public class S7UserEvent extends S7SysEvent {
+import org.w3c.dom.Attr;
+import org.xmlunit.util.Predicate;
- public S7UserEvent(S7PayloadDiagnosticMessage payload) {
- super(payload);
- map.put(Fields.TYPE.name(), "USER");
+/**
+ * SPI element needed to exclude our custom attributes from comparison of XML
results.
+ */
+public class SkipAttributeFilter implements Predicate<Attr> {
+
+ public static final String IGNORE_ATTRIBUTE_NAME = "plc4x-skip-comparison";
+
+ @Override
+ public boolean test(Attr attr) {
+ return !IGNORE_ATTRIBUTE_NAME.equals(attr.getName());
}
+
}
diff --git
a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipDifferenceEvaluator.java
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipDifferenceEvaluator.java
new file mode 100644
index 0000000000..8e5e0f7cab
--- /dev/null
+++
b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipDifferenceEvaluator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.test.driver.xmlunit;
+
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xmlunit.diff.Comparison;
+import org.xmlunit.diff.ComparisonResult;
+import org.xmlunit.diff.DifferenceEvaluator;
+
+/**
+ * Evaluator of differences which allows to ignore differences for elements
annotated with 'plc4x-skip-comparison' attribute.
+ */
+public class SkipDifferenceEvaluator implements DifferenceEvaluator {
+
+ @Override
+ public ComparisonResult evaluate(Comparison comparison, ComparisonResult
comparisonResult) {
+ if (comparisonResult != ComparisonResult.EQUAL) {
+ Node target = comparison.getControlDetails().getTarget();
+
+ // root element
+ if (target == null || target.getParentNode() == null) {
+ return comparisonResult;
+ }
+
+ // verify parent element - help with text nodes
+ NamedNodeMap attributes = target.getParentNode().getAttributes();
+ if (attributes != null) {
+ Node attribute =
attributes.getNamedItem(SkipAttributeFilter.IGNORE_ATTRIBUTE_NAME);
+ if (attribute != null) {
+ String content = attribute.getTextContent();
+ return Boolean.parseBoolean(content.trim()) ?
ComparisonResult.EQUAL : comparisonResult;
+ }
+ }
+ }
+
+ return comparisonResult;
+ }
+}
diff --git a/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
b/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
index 3770e9d267..332722e461 100644
--- a/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
+++ b/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
@@ -1239,6 +1239,18 @@
</PlcResponseItem>
</hurz>
</values>
+ <metadata isList="true">
+ <hurz>
+ <entry>
+ <key dataType="string" bitLength="17"
encoding="UTF-8">receive_timestamp</key>
+ <value dataType="string" bitLength="13" encoding="UTF-8"
plc4x-skip-comparison="true">0</value>
+ </entry>
+ <entry>
+ <key dataType="string" bitLength="16"
encoding="UTF-8">timestamp_source</key>
+ <value dataType="string" bitLength="10"
encoding="UTF-8">ASSUMPTION</value>
+ </entry>
+ </hurz>
+ </metadata>
</PlcReadResponse>
</api-response>
</steps>