diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java new file mode 100644 index 00000000000..e88ca207f32 --- /dev/null +++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/messages/PlcMetadataKeys.java @@ -0,0 +1,35 @@ +/* + * 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.messages; + +import org.apache.plc4x.java.api.metadata.Metadata.Key; +import org.apache.plc4x.java.api.metadata.time.TimeSource; + +/** + * High level definition of common metadata keys which can occur across multiple drivers. + */ +public interface PlcMetadataKeys { + + Key TIMESTAMP = Key.of("timestamp", Long.class); + Key TIMESTAMP_SOURCE = Key.of("timestamp_source", TimeSource.class); + + Key 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 3aa2d642deb..1feae9f3bbb 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 00000000000..484fad7148a --- /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> keys() { + return Set.of(); + } + + @Override + public Map, Object> entries() { + return Map.of(); + } + + @Override + public Object getValue(Key key) { + return null; + } + + }; + + class Key { + + private final String key; + private final Class type; + + protected Key(String key, Class type) { + this.key = key; + this.type = type; + } + + public String getKey() { + return key; + } + + public boolean validate(Object value) { + return type.isInstance(value); + } + + public static Key of(String key, Class 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> keys(); + Map, Object> entries(); + Object getValue(Key key); +} diff --git a/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java new file mode 100644 index 00000000000..84e5843635d --- /dev/null +++ b/plc4j/api/src/main/java/org/apache/plc4x/java/api/metadata/time/TimeSource.java @@ -0,0 +1,33 @@ +/* + * 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.time; + +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 + +} 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 08eaa141d06..7a58931ae7e 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.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 @@ protected CompletableFuture singleRead(PlcReadRequest readReque .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 @@ protected CompletableFuture multiRead(PlcReadRequest readReques .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 @@ protected CompletableFuture multiRead(PlcReadRequest readReques return future; } - protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest readRequest, Map resolvedTags, AmsPacket adsData) { + protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest readRequest, Map resolvedTags, AmsPacket adsData, Metadata responseMetadata) { ReadBuffer readBuffer = null; + Map metadata = new HashMap<>(); Map responseCodes = new HashMap<>(); // Read the response codes first @@ -841,6 +854,7 @@ protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest readRequest, if (readBuffer != null) { Map> 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 @@ protected PlcReadResponse convertToPlc4xReadResponse(PlcReadRequest readRequest, values.put(tagName, parseResponseItem(directAdsTag, readBuffer)); } } - return new DefaultPlcReadResponse(readRequest, values); + return new DefaultPlcReadResponse(readRequest, values, metadata); } return null; } @@ -1071,8 +1085,13 @@ protected CompletableFuture singleWrite(PlcWriteRequest writeR .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 @@ protected CompletableFuture multiWrite(PlcWriteRequest writeRe .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 @@ else if (!dataType.getChildren().isEmpty()) { } } - protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest writeRequest, Map resolvedTags, AmsPacket adsData) { + protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest writeRequest, Map resolvedTags, AmsPacket adsData, Metadata eventMtadata) { Map responseCodes = new HashMap<>(); + Map metadata = new HashMap<>(); if (adsData instanceof AdsWriteResponse) { AdsWriteResponse adsWriteResponse = (AdsWriteResponse) adsData; responseCodes.put(writeRequest.getTagNames().stream().findFirst().orElse(""), @@ -1256,6 +1282,9 @@ protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest writeRequ // 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 @@ protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest writeRequ } } - return new DefaultPlcWriteResponse(writeRequest, responseCodes); + return new DefaultPlcWriteResponse(writeRequest, responseCodes, metadata); } @Override @@ -1493,9 +1522,16 @@ protected void decode(ConversationContext context, AmsTCPPacket ms if (msg.getUserdata() instanceof AdsDeviceNotificationRequest) { AdsDeviceNotificationRequest notificationData = (AdsDeviceNotificationRequest) msg.getUserdata(); List 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 samples = stamp.getAdsNotificationSamples(); for (AdsNotificationSample sample : samples) { long handle = sample.getNotificationHandle(); @@ -1503,10 +1539,12 @@ protected void decode(ConversationContext context, AmsTCPPacket ms 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 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 @@ protected void decode(ConversationContext context, AmsTCPPacket ms } } - private Map> convertSampleToPlc4XResult(AdsSubscriptionHandle subscriptionHandle, byte[] data) throws + private Map> convertSampleToPlc4XResult(AdsSubscriptionHandle subscriptionHandle, byte[] data, Map tagMetadata, Metadata metadata) throws ParseException { Map> 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/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java new file mode 100644 index 00000000000..713081159ca --- /dev/null +++ b/plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/OpcMetadataKeys.java @@ -0,0 +1,36 @@ +/* + * 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; + +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; + +/** + * OPC UA level metadata keys. + */ +public interface OpcMetadataKeys { + + Key QUALITY = Metadata.Key.of("opcua_quality", OpcuaQualityStatus.class); + + Key SERVER_TIMESTAMP = Metadata.Key.of("opcua_server_timestamp", Long.class); + Key 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 91112aef604..0ccf130a5b3 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.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 CompletableFuture read(PlcReadRequest readRequest) { List readValueArray = new ArrayList<>(request.getTagNames().size()); Iterator iterator = request.getTagNames().iterator(); - Map tagMap = new HashMap<>(); + Map 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 CompletableFuture read(PlcReadRequest readRequest) { 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 metadata = new LinkedHashMap<>(); + return new DefaultPlcReadResponse(request, readResponse(tagMap, response.getResults(), metadata, responseMetadata), metadata); + }); } static NodeId generateNodeId(OpcuaTag tag) { @@ -262,15 +273,16 @@ static NodeId generateNodeId(OpcuaTag tag) { return nodeId; } - public Map> readResponse(LinkedHashSet tagNames, Map tagMap, List results) { + public Map> readResponse(Map tagMap, List results, Map metadata, Metadata responseMetadata) { PlcResponseCode responseCode = null; // initialize variable Map> 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 Map> readResponse(LinkedHashSet 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 @@ private static void bridge(RequestTransaction transaction, CompletableFuture 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 ab241b0411e..4889480f3cd 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.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.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 void stopSubscriber() { * @param values - array of data values to be sent to the client. */ private void onSubscriptionValue(MonitoredItemNotification[] values) { - LinkedHashSet tagNameList = new LinkedHashSet<>(); + long receiveTs = System.currentTimeMillis(); + Metadata responseMetadata = new DefaultMetadata.Builder() + .put(PlcMetadataKeys.RECEIVE_TIMESTAMP, receiveTs) + .build(); + List dataValues = new ArrayList<>(values.length); Map 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> tags = plcSubscriber.readResponse(tagNameList, tagMap, dataValues); - final PlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(Instant.now(), tags); + + Map metadata = new HashMap<>(); + Map> 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 00000000000..1500e210999 --- /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 931bfab0a85..5d08cc6104e 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.List; import java.util.Map; -public class S7AlarmEvent implements S7Event { +public class S7AlarmEvent extends S7EventBase { public enum Fields { @@ -130,172 +131,19 @@ public enum Fields { } - private final Instant timeStamp; private final Map 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 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 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 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 obj) { + super(timestamp); + this.map = obj; } - ; - @Override public Map getMap() { return map; } - @Override - public Instant getTimestamp() { - return timeStamp; - } - @Override public PlcReadRequest getRequest() { throw new UnsupportedOperationException("Not supported yet."); @@ -671,5 +519,160 @@ public PlcResponseCode getResponseCode(String name) { 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 map = new HashMap<>(); + map.put(S7SysEvent.Fields.TIMESTAMP.name(), timeStamp); + + List 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 map = new HashMap<>(); + map.put(S7SysEvent.Fields.TIMESTAMP.name(), timeStamp); + + List 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 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 6b7c83d3bf2..9c0d0fcd83b 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.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.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 enum Fields { TRANSPORTSIZE_, DATA_ } - private final PlcSubscriptionRequest request; - - private final Instant timeStamp; private final Map 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 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 PlcValue getPlcValue(String name) { } 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 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 boolean isValidBoolean(String name, int index) { 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 Boolean getBoolean(String name, int index) { ByteBuf byteBuf = Unpooled.wrappedBuffer((byte[]) map.get(name)); return byteBuf.getBoolean(index); } - @Override public Collection 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 boolean isValidByte(String name, int index) { 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 Byte getByte(String name, int index) { int pos = index * Byte.BYTES; return byteBuf.getByte(pos); } - @Override public Collection getAllBytes(String name) { if (!(map.get(name) instanceof byte[])) { @@ -264,12 +244,10 @@ public Collection getAllBytes(String name) { 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 boolean isValidShort(String name, int index) { 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 Short getShort(String name, int index) { int pos = index * Short.BYTES; return byteBuf.getShort(pos); } - @Override public Collection getAllShorts(String name) { if (!(map.get(name) instanceof byte[])) { @@ -307,12 +282,10 @@ public Collection getAllShorts(String name) { } 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 boolean isValidInteger(String name, int index) { 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 Integer getInteger(String name, int index) { int pos = index * Integer.BYTES; return byteBuf.getInt(pos); } - @Override public Collection getAllIntegers(String name) { if (!(map.get(name) instanceof byte[])) { @@ -350,37 +320,30 @@ public Collection getAllIntegers(String name) { } 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 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 boolean isValidLong(String name, int index) { 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 Long getLong(String name, int index) { int pos = index * Long.BYTES; return byteBuf.getLong(pos); } - @Override public Collection getAllLongs(String name) { if (!(map.get(name) instanceof byte[])) { @@ -418,12 +378,10 @@ public Collection getAllLongs(String name) { } 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 boolean isValidFloat(String name, int index) { 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 Float getFloat(String name, int index) { int pos = index * Float.BYTES; return byteBuf.getFloat(pos); } - @Override public Collection getAllFloats(String name) { if (!(map.get(name) instanceof byte[])) { @@ -461,12 +416,10 @@ public Collection getAllFloats(String name) { } 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 boolean isValidDouble(String name, int index) { 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 Double getDouble(String name, int index) { int pos = index * Double.BYTES; return byteBuf.getDouble(pos); } - @Override public Collection getAllDoubles(String name) { if (!(map.get(name) instanceof byte[])) { @@ -504,37 +454,30 @@ public Collection getAllDoubles(String name) { } 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 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 boolean isValidString(String name, int index) { return false; } } - @Override public String getString(String name) { if (!(map.get(name) instanceof byte[])) { @@ -553,22 +495,18 @@ public String getString(String name) { 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 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 boolean isValidTime(String name, int index) { 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 LocalTime getTime(String name, int index) { Duration dr = StaticHelper.s7TimeToDuration(value); return LocalTime.of(dr.toHoursPart(), dr.toMinutesPart(), dr.toSecondsPart(), dr.toNanosPart()); } - @Override public Collection getAllTimes(String name) { if (!(map.get(name) instanceof byte[])) { @@ -613,12 +548,10 @@ public Collection getAllTimes(String name) { } 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 boolean isValidDate(String name, int index) { 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 LocalDate getDate(String name, int index) { short value = byteBuf.getShort(pos); return StaticHelper.s7DateToLocalDate(value); } - @Override public Collection getAllDates(String name) { if (!(map.get(name) instanceof byte[])) { @@ -658,12 +588,10 @@ public Collection getAllDates(String name) { } 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 boolean isValidDateTime(String name, int index) { 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 LocalDateTime getDateTime(String name, int index) { int pos = index * Long.BYTES; return StaticHelper.s7DateTimeToLocalDateTime(byteBuf.slice(pos, Long.BYTES)); } - @Override public Collection getAllDateTimes(String name) { if (!(map.get(name) instanceof byte[])) { @@ -702,22 +627,18 @@ public Collection getAllDateTimes(String name) { } return items; } - @Override public Collection 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 boolean equals(Object obj) { 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 boolean equals(Object obj) { return false; } }; - + return true; } @@ -747,23 +668,22 @@ public boolean equals(Object obj) { private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest request, List 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 @@ private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest re 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 @@ private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest re 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 @@ private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest re 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 @@ private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest re 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 @@ private static PlcValue dataToPlcValue(String tagname, PlcSubscriptionRequest re 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 00000000000..7580b72923d --- /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 b144ad8510f..4a9d53f204b 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.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 enum Fields { CURRENT_MODE } - private final Instant timeStamp; private final Map 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 Map getMap() { 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 4fab9b97ac0..364adc443d9 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.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 enum Fields { INFO2 } - private final Instant timeStamp; protected final Map 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 map) { + super(instant); + this.map = map; } @Override @@ -76,11 +57,6 @@ public Map getMap() { return map; } - @Override - public Instant getTimestamp() { - return timeStamp; - } - @Override public PlcReadRequest getRequest() { throw new UnsupportedOperationException("Not supported yet."); @@ -456,4 +432,27 @@ public PlcResponseCode getResponseCode(String name) { throw new UnsupportedOperationException("Not supported yet."); } + public static S7SysEvent of(S7PayloadDiagnosticMessage payload) { + Map 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 d9f01d0dbdb..d5805fc25eb 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 map) { + super(instant, map); + } + + public static S7UserEvent of(S7PayloadDiagnosticMessage payload) { + S7SysEvent event = S7SysEvent.of(payload); + Map 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 8f4eee69c29..97aaa7835a9 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 @@ protected void decode(ConversationContext context, TPKTPacket msg) t 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 @@ protected void decode(ConversationContext context, TPKTPacket msg) t (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 5983e519f6f..f0ee410501b 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> values; + private final Map metadata; public DefaultPlcReadResponse(PlcReadRequest request, Map> values) { + this(request, values, Collections.emptyMap()); + } + + public DefaultPlcReadResponse(PlcReadRequest request, + Map> values, + Map metadata) { this.request = request; this.values = values; + this.metadata = Collections.unmodifiableMap(metadata); } @Override @@ -58,6 +69,11 @@ public PlcReadRequest getRequest() { return request; } + @Override + public Metadata getTagMetadata(String tag) { + return metadata.getOrDefault(tag, DefaultMetadata.EMPTY); + } + @Override public PlcValue getAsPlcValue() { Map structMap = new HashMap<>(); @@ -669,6 +685,20 @@ public void serialize(WriteBuffer writeBuffer) throws SerializationException { } writeBuffer.popContext("values"); + if (metadata != null && !metadata.isEmpty()) { + writeBuffer.pushContext("metadata", WithRenderAsList(true)); + + for (Entry 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 2035f76e001..e4ef1835aa0 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> tags) { - super(null, tags); + Map> tags) { + this(timestamp, tags, Collections.emptyMap()); + } + + public DefaultPlcSubscriptionEvent(Instant timestamp, + Map> tags, + Map 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 329792956dd..96ff7ad066e 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 responseCodes; + private final Map metadata; public DefaultPlcWriteResponse(PlcWriteRequest request, Map responseCodes) { + this(request, responseCodes, Collections.emptyMap()); + } + + public DefaultPlcWriteResponse(PlcWriteRequest request, + Map responseCodes, + Map metadata) { this.request = request; this.responseCodes = responseCodes; + this.metadata = metadata; } @Override @@ -48,6 +59,11 @@ public PlcWriteRequest getRequest() { return request; } + @Override + public Metadata getTagMetadata(String tag) { + return metadata.getOrDefault(tag, Metadata.EMPTY); + } + @Override public Collection getTagNames() { return request.getTagNames(); @@ -83,6 +99,20 @@ public void serialize(WriteBuffer writeBuffer) throws SerializationException { } writeBuffer.popContext("responseCodes"); + if (metadata != null && !metadata.isEmpty()) { + writeBuffer.pushContext("metadata", WithRenderAsList(true)); + + for (Entry 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 00000000000..55bd78b4227 --- /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, Object> values; + + DefaultMetadata(Map, Object> values) { + this(values, Metadata.EMPTY); + } + + public DefaultMetadata(Map, Object> values, Metadata parent) { + this.values = new LinkedHashMap<>(values); + this.parent = Objects.requireNonNull(parent, "Parent metadata must not be null"); + } + + @Override + public Set> keys() { + Set> keys = new LinkedHashSet<>(values.keySet()); + keys.addAll(parent.keys()); + return Collections.unmodifiableSet(keys); + } + + @Override + public Map, Object> entries() { + Map, 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, Object> values = new LinkedHashMap<>(); + private final Metadata parent; + + public Builder() { + this(DefaultMetadata.EMPTY); + } + + public Builder(Metadata parent) { + this.parent = parent; + } + + public Builder put(Key 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 ddf6439c34b..1d538223bd3 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.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/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java new file mode 100644 index 00000000000..d7f63abff17 --- /dev/null +++ b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/driver/xmlunit/SkipAttributeFilter.java @@ -0,0 +1,37 @@ +/* + * 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.Attr; +import org.xmlunit.util.Predicate; + +/** + * SPI element needed to exclude our custom attributes from comparison of XML results. + */ +public class SkipAttributeFilter implements Predicate { + + 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 00000000000..8e5e0f7cabb --- /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 3770e9d267e..332722e461e 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 @@ + + + + receive_timestamp + 0 + + + timestamp_source + ASSUMPTION + + +