From 96fd934b6cf92a9f2ed85a75d869c6cc509ba120 Mon Sep 17 00:00:00 2001 From: tbischoff2 <42869259+tbischoff2@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:06:44 +0200 Subject: [PATCH] OPC UA Endpoint: additional datatypes (#850) * add support for missing datatypes in OPC UA endpoint * update changelog * update documentation --------- Co-authored-by: Michael Jacoby --- docs/source/interfaces/endpoint.md | 46 +++- docs/source/other/release-notes.md | 3 + .../endpoint/opcua/ValueConverter.java | 246 +++++++++++++----- .../opcua/creator/PropertyCreator.java | 2 +- .../helper/AasSubmodelElementHelper.java | 240 +++++++++++++++-- .../endpoint/opcua/helper/UaHelper.java | 2 +- .../listener/AasServiceIoManagerListener.java | 35 ++- .../endpoint/opcua/OpcUaEndpointFullTest.java | 56 ++++ .../endpoint/opcua/OpcUaEndpointTest.java | 16 +- .../endpoint/opcua/helper/TestConstants.java | 2 + .../endpoint/opcua/helper/TestUtils.java | 33 +++ .../model/value/primitive/TimeValue.java | 2 +- .../ilt/faaast/service/model/AASFull.java | 6 + .../model/value/primitive/TimeValueTest.java | 1 + 14 files changed, 577 insertions(+), 113 deletions(-) diff --git a/docs/source/interfaces/endpoint.md b/docs/source/interfaces/endpoint.md index d012f9de1..ccbe5031e 100644 --- a/docs/source/interfaces/endpoint.md +++ b/docs/source/interfaces/endpoint.md @@ -161,7 +161,6 @@ For evaluation purposes, you also have the possibility to request an [evaluation However, this is not necessary for using the OPC UA Endpoint we already provide a pre-compiled version that is used by default when building FA³ST Service from code. The developers of the Prosys OPC UA SDK have been so kind to allow us to publish that pre-compiled version as part of this open-source project under the condition that all classes related to their SDK are obfuscated. - ### Configuration Parameters OPC UA Endpoint configuration supports the following configuration parameters @@ -231,6 +230,7 @@ These directories contain the following subdirectories: //... } ``` + ### OPC UA Client Libraries To connect to the OPC UA Endpoint, you need an OPC UA Client. Here are some example libraries and tools you can use: @@ -240,19 +240,56 @@ To connect to the OPC UA Endpoint, you need an OPC UA Client. Here are some exam - [Prosys OPC UA Browser](https://www.prosysopc.com/products/opc-ua-browser/): Free OPC UA test client (registration on website required for download). - [Official Samples from the OPC Foundation](https://github.com/OPCFoundation/UA-.NETStandard-Samples): C#-based sample code from the OPC Foundation. - - ```{figure} ../images/opc-ua-endpoint.png :width: 800px :align: center Screenshot showing UaExpert connected to a FA³ST Service via OPC UA Endpoint. ``` +### Datatype mapping + +It's necessary to map the AAS datatypes to OPC UA datatypes. In some cases, no corresponding datatype is available in OPC UA. +These datatypes are mapped to String. + +The mapping is as follows + +| AAS datatype | OPC UA datatype | Comment | +| ------------------------------- | -------------------- | ----------------------------------------- | +| xs:string | String | | +| xs:boolean | Boolean | | +| xs:decimal | String | | +| xs:integer | String | | +| xs:double | Double | No distinction between +0.0 and -0.0 | +| xs:float | Float | No distinction between +0.0 and -0.0 | +| xs:date | String | | +| xs:dateTime | DateTime | Converted to UTC, TZ offset is lost. | +| xs:time | String | | +| xs:gYear | String | | +| xs:gMonth | String | | +| xs:gDay | String | | +| xs:gYearMonth | String | | +| xs:gMonthDay | String | | +| xs:duration | String | | +| xs:byte | SByte | | +| xs:short | Int16 | | +| xs:int | Int32 | | +| xs:long | Int64 | | +| xs:unsignedByte | Byte | | +| xs:unsignedShort | UInt16 | | +| xs:unsignedInt | UInt32 | | +| xs:unsignedLong | UInt64 | | +| xs:positiveInteger | String | | +| xs:nonNegativeInteger | String | | +| xs:negativeInteger | String | | +| xs:nonPositiveInteger | String | | +| xs:hexBinary | ByteString | | +| xs:base64Binary | ByteString | | +| xs:anyURI | String | | + ### API As stated, there is currently no official mapping of the AAS API to OPC UA for AAS v3.0 but FA³ST Service implements its proprietary adaption of the mapping for AAS v2.0. - #### Supported Functionality - Writing values for the following types @@ -265,7 +302,6 @@ As stated, there is currently no official mapping of the AAS API to OPC UA for A - Entity - Operations (OPC UA method calls). Exception: Inoutput-Variables are not supported in OPC UA. - #### Not (yet) Supported Functionality - Updating the model, i.e., adding new elements at runtime is not possible diff --git a/docs/source/other/release-notes.md b/docs/source/other/release-notes.md index ef296754a..e898115a4 100644 --- a/docs/source/other/release-notes.md +++ b/docs/source/other/release-notes.md @@ -3,6 +3,9 @@ ## 1.2.0-SNAPSHOT (current development version) **New Features & Major Changes** +- Endpoint + - OPC UA + - Added support for all datatypes of the AAS specification **Internal changes & bugfixes** - General diff --git a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/ValueConverter.java b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/ValueConverter.java index e0b8f2a4b..f6bb52dda 100644 --- a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/ValueConverter.java +++ b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/ValueConverter.java @@ -29,15 +29,23 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.value.Datatype; import de.fraunhofer.iosb.ilt.faaast.service.model.value.PropertyValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.TypedValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.TypedValueFactory; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.AbstractDateTimeValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.DateTimeValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.DecimalValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.DurationValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.IntegerValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.NegativeIntegerValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.NonNegativeIntegerValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.NonPositiveIntegerValue; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.PositiveIntegerValue; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Base64; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -121,22 +129,36 @@ public TypeMapper(A aasObject, O opcuaObject) { static { typeList = new ArrayList<>(); - typeList.add(new DatatypeMapper(Identifiers.ByteString, null, AASDataTypeDefXsd.Base64Binary)); + typeList.add(new DatatypeMapper(Identifiers.ByteString, Datatype.BASE64_BINARY, AASDataTypeDefXsd.Base64Binary)); + typeList.add(new DatatypeMapper(Identifiers.ByteString, Datatype.HEX_BINARY, AASDataTypeDefXsd.HexBinary)); typeList.add(new DatatypeMapper(Identifiers.Boolean, Datatype.BOOLEAN, AASDataTypeDefXsd.Boolean)); typeList.add(new DatatypeMapper(Identifiers.DateTime, Datatype.DATE_TIME, AASDataTypeDefXsd.DateTime)); - typeList.add(new DatatypeMapper(Identifiers.Decimal, Datatype.DECIMAL, AASDataTypeDefXsd.Decimal)); - typeList.add(new DatatypeMapper(Identifiers.Integer, Datatype.INTEGER, AASDataTypeDefXsd.Integer)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.DECIMAL, AASDataTypeDefXsd.Decimal)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.INTEGER, AASDataTypeDefXsd.Integer)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.POSITIVE_INTEGER, AASDataTypeDefXsd.PositiveInteger)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.NON_NEGATIVE_INTEGER, AASDataTypeDefXsd.NonNegativeInteger)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.NEGATIVE_INTEGER, AASDataTypeDefXsd.NegativeInteger)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.NON_POSITIVE_INTEGER, AASDataTypeDefXsd.NonPositiveInteger)); typeList.add(new DatatypeMapper(Identifiers.Int32, Datatype.INT, AASDataTypeDefXsd.Int)); - typeList.add(new DatatypeMapper(Identifiers.UInt32, null, AASDataTypeDefXsd.UnsignedInt)); + typeList.add(new DatatypeMapper(Identifiers.UInt32, Datatype.UNSIGNED_INT, AASDataTypeDefXsd.UnsignedInt)); typeList.add(new DatatypeMapper(Identifiers.Int64, Datatype.LONG, AASDataTypeDefXsd.Long)); - typeList.add(new DatatypeMapper(Identifiers.UInt64, null, AASDataTypeDefXsd.UnsignedLong)); + typeList.add(new DatatypeMapper(Identifiers.UInt64, Datatype.UNSIGNED_LONG, AASDataTypeDefXsd.UnsignedLong)); typeList.add(new DatatypeMapper(Identifiers.Int16, Datatype.SHORT, AASDataTypeDefXsd.Short)); - typeList.add(new DatatypeMapper(Identifiers.UInt16, null, AASDataTypeDefXsd.UnsignedShort)); + typeList.add(new DatatypeMapper(Identifiers.UInt16, Datatype.UNSIGNED_SHORT, AASDataTypeDefXsd.UnsignedShort)); typeList.add(new DatatypeMapper(Identifiers.SByte, Datatype.BYTE, AASDataTypeDefXsd.Byte)); - typeList.add(new DatatypeMapper(Identifiers.Byte, null, AASDataTypeDefXsd.UnsignedByte)); + typeList.add(new DatatypeMapper(Identifiers.Byte, Datatype.UNSIGNED_BYTE, AASDataTypeDefXsd.UnsignedByte)); typeList.add(new DatatypeMapper(Identifiers.Double, Datatype.DOUBLE, AASDataTypeDefXsd.Double)); typeList.add(new DatatypeMapper(Identifiers.Float, Datatype.FLOAT, AASDataTypeDefXsd.Float)); typeList.add(new DatatypeMapper(Identifiers.String, Datatype.STRING, AASDataTypeDefXsd.String)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.ANY_URI, AASDataTypeDefXsd.AnyUri)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.DATE, AASDataTypeDefXsd.Date)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.TIME, AASDataTypeDefXsd.Time)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.DURATION, AASDataTypeDefXsd.Duration)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.GDAY, AASDataTypeDefXsd.GDay)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.GMONTH, AASDataTypeDefXsd.GMonth)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.GMONTH_DAY, AASDataTypeDefXsd.GMonthDay)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.GYEAR, AASDataTypeDefXsd.GYear)); + typeList.add(new DatatypeMapper(Identifiers.String, Datatype.GYEAR_MONTH, AASDataTypeDefXsd.GYearMonth)); MODELING_KIND_MAP = new EnumMap<>(ModellingKind.class); MODELING_KIND_MAP.put(ModellingKind.INSTANCE, AASModellingKindDataType.Instance); @@ -503,9 +525,10 @@ public static Reference getReferenceFromKeys(AASKeyDataType[] value) { * * @param data The desired SubmodelElementData. * @param dv The desired Value. + * @return Shows whether the value is valid (true) or not (false). */ - public static void setSubmodelElementValue(SubmodelElementData data, DataValue dv) { - setSubmodelElementValue(data.getSubmodelElement(), data.getType(), dv.getValue()); + public static boolean setSubmodelElementValue(SubmodelElementData data, DataValue dv) { + return setSubmodelElementValue(data.getSubmodelElement(), data.getType(), dv.getValue()); } @@ -515,95 +538,67 @@ public static void setSubmodelElementValue(SubmodelElementData data, DataValue d * @param submodelElement The desired SubmodelElement. * @param type The desired type. * @param variant The desired Value. + * @return Shows whether the value is valid (true) or not (false). */ - public static void setSubmodelElementValue(SubmodelElement submodelElement, SubmodelElementData.Type type, Variant variant) { + public static boolean setSubmodelElementValue(SubmodelElement submodelElement, SubmodelElementData.Type type, Variant variant) { + boolean retval = true; switch (type) { case PROPERTY_VALUE: { Property aasProp = (Property) submodelElement; - String newValue = convertVariantValueToString(variant); + String newValue = convertVariantValueToString(variant, aasProp.getValueType()); + retval = checkValue(aasProp.getValueType(), newValue); aasProp.setValue(newValue); break; } case RANGE_MIN: { Range aasRange = (Range) submodelElement; - String newValue = convertVariantValueToString(variant); + String newValue = convertVariantValueToString(variant, aasRange.getValueType()); + retval = checkValue(aasRange.getValueType(), newValue); aasRange.setMin(newValue); break; } case RANGE_MAX: { Range aasRange = (Range) submodelElement; - String newValue = convertVariantValueToString(variant); + String newValue = convertVariantValueToString(variant, aasRange.getValueType()); + retval = checkValue(aasRange.getValueType(), newValue); aasRange.setMax(newValue); break; } case BLOB_VALUE: { - Blob aasBlob = (Blob) submodelElement; - ByteString bs = null; - if (variant.getValue() != null) { - bs = (ByteString) variant.getValue(); - } - aasBlob.setValue(ByteString.asByteArray(bs)); + setBlobValue(submodelElement, variant); break; } case MULTI_LANGUAGE_VALUE: { - MultiLanguageProperty aasMultiProp = (MultiLanguageProperty) submodelElement; - if (variant.isArray() && (variant.getValue() instanceof LocalizedText[])) { - aasMultiProp.setValue(ValueConverter.getLangStringSetFromLocalizedText((LocalizedText[]) variant.getValue())); - } - else if (variant.isEmpty()) { - aasMultiProp.setValue(new ArrayList<>()); - } + setMultiLanguageValue(submodelElement, variant); break; } case REFERENCE_ELEMENT_VALUE: { - ReferenceElement aasRefElem = (ReferenceElement) submodelElement; - if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { - aasRefElem.setValue(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); - } - else if (variant.isEmpty()) { - aasRefElem.setValue(null); - } + setReferenceElementValue(submodelElement, variant); break; } case RELATIONSHIP_ELEMENT_FIRST: { - RelationshipElement aasRelElem = (RelationshipElement) submodelElement; - if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { - aasRelElem.setFirst(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); - } - else if (variant.isEmpty()) { - aasRelElem.setFirst(null); - } + setRelationshipElementFirstValue(submodelElement, variant); break; } case RELATIONSHIP_ELEMENT_SECOND: { - RelationshipElement aasRelElem = (RelationshipElement) submodelElement; - if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { - aasRelElem.setSecond(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); - } - else if (variant.isEmpty()) { - aasRelElem.setSecond(null); - } + setRelationshipElementSecondValue(submodelElement, variant); break; } case ENTITY_GLOBAL_ASSET_ID: { Entity aasEntity = (Entity) submodelElement; - aasEntity.setGlobalAssetId(convertVariantValueToString(variant)); + aasEntity.setGlobalAssetId(convertVariantValueToString(variant, DataTypeDefXsd.STRING)); break; } case ENTITY_TYPE: { - Entity aasEntity = (Entity) submodelElement; - if (variant.isEmpty()) { - aasEntity.setEntityType(null); - } - else { - aasEntity.setEntityType(ValueConverter.getEntityType(AASEntityTypeDataType.valueOf((int) variant.getValue()))); - } + setEntityValue(submodelElement, variant); break; } default: LOGGER.warn("setSubmodelElementValue: SubmodelElement {}: unkown type {}", submodelElement.getIdShort(), type); throw new IllegalArgumentException("unkown type " + type); } + + return retval; } @@ -713,12 +708,24 @@ public static DateTime createDateTime(LocalDateTime value) { } - private static String convertVariantValueToString(Variant variant) { + private static String convertVariantValueToString(Variant variant, DataTypeDefXsd type) { String retval = ""; if (variant.getValue() != null) { - // special treatment for DateTime - if (variant.getValue() instanceof DateTime dateTime) { - retval = OffsetDateTime.ofInstant(dateTime.toInstant(), ZoneId.systemDefault()).toString(); + // special treatment for DateTime and ByteString + if (variant.getValue() instanceof DateTime dt) { + retval = OffsetDateTime.ofInstant(dt.toInstant(), ZoneId.systemDefault()).toString(); + } + else if (variant.getValue() instanceof ByteString bt) { + if (type == DataTypeDefXsd.HEX_BINARY) { + retval = bt.toHex(); + // we must remove the '0x' at the beginning + if ((retval != null) && (retval.startsWith("0x"))) { + retval = retval.substring(2); + } + } + else { + retval = Base64.getEncoder().encodeToString(bt.getValue()); + } } else { retval = variant.getValue().toString(); @@ -756,11 +763,32 @@ public static Object convertTypedValue(TypedValue typedValue) throws NumberFo return null; } Object retval = typedValue.getValue(); - if ((typedValue instanceof DecimalValue) || (typedValue instanceof IntegerValue)) { - retval = Long.valueOf(retval.toString()); + if (typedValue instanceof IntegerValue integerValue) { + retval = integerValue.asString(); + } + else if (typedValue instanceof DurationValue dv) { + retval = dv.asString(); + } + else if (typedValue instanceof DecimalValue decimalValue) { + retval = decimalValue.asString(); + } + else if (typedValue instanceof PositiveIntegerValue positiveIntegerValue) { + retval = positiveIntegerValue.asString(); + } + else if (typedValue instanceof NonPositiveIntegerValue nonPositiveIntegerValue) { + retval = nonPositiveIntegerValue.asString(); } - else if (typedValue instanceof DateTimeValue) { - retval = ValueConverter.createDateTime((OffsetDateTime) retval); + else if (typedValue instanceof NegativeIntegerValue negativeIntegerValue) { + retval = negativeIntegerValue.asString(); + } + else if (typedValue instanceof NonNegativeIntegerValue nonNegativeIntegerValue) { + retval = nonNegativeIntegerValue.asString(); + } + else if (typedValue instanceof DateTimeValue dateTimeValue) { + retval = convertDateTime(dateTimeValue); + } + else if (typedValue instanceof AbstractDateTimeValue sbstractDateTimeValue) { + retval = sbstractDateTimeValue.asString(); } return retval; } @@ -828,4 +856,96 @@ public static AASSubmodelElementsDataType getAasSubmodelElementsType(AasSubmodel return retval; } + + private static Object convertDateTime(TypedValue typedValue) { + Object retval; + if ((typedValue.getDataType() == Datatype.DATE) || (typedValue.getDataType() == Datatype.DATE_TIME)) { + retval = ValueConverter.createDateTime((OffsetDateTime) typedValue.getValue()); + } + else { + retval = typedValue.asString(); + } + return retval; + } + + + private static boolean checkValue(DataTypeDefXsd datatype, String value) { + boolean retval; + + try { + TypedValueFactory.create(datatype, value); + retval = true; + } + catch (Exception ex) { + retval = false; + } + + return retval; + } + + + private static void setEntityValue(SubmodelElement submodelElement, Variant variant) { + Entity aasEntity = (Entity) submodelElement; + if (variant.isEmpty()) { + aasEntity.setEntityType(null); + } + else { + aasEntity.setEntityType(ValueConverter.getEntityType(AASEntityTypeDataType.valueOf((int) variant.getValue()))); + } + } + + + private static void setRelationshipElementSecondValue(SubmodelElement submodelElement, Variant variant) { + RelationshipElement aasRelElem = (RelationshipElement) submodelElement; + if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { + aasRelElem.setSecond(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); + } + else if (variant.isEmpty()) { + aasRelElem.setSecond(null); + } + } + + + private static void setRelationshipElementFirstValue(SubmodelElement submodelElement, Variant variant) { + RelationshipElement aasRelElem = (RelationshipElement) submodelElement; + if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { + aasRelElem.setFirst(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); + } + else if (variant.isEmpty()) { + aasRelElem.setFirst(null); + } + } + + + private static void setReferenceElementValue(SubmodelElement submodelElement, Variant variant) { + ReferenceElement aasRefElem = (ReferenceElement) submodelElement; + if (variant.isArray() && (variant.getValue() instanceof AASKeyDataType[])) { + aasRefElem.setValue(ValueConverter.getReferenceFromKeys((AASKeyDataType[]) variant.getValue())); + } + else if (variant.isEmpty()) { + aasRefElem.setValue(null); + } + } + + + private static void setMultiLanguageValue(SubmodelElement submodelElement, Variant variant) { + MultiLanguageProperty aasMultiProp = (MultiLanguageProperty) submodelElement; + if (variant.isArray() && (variant.getValue() instanceof LocalizedText[])) { + aasMultiProp.setValue(ValueConverter.getLangStringSetFromLocalizedText((LocalizedText[]) variant.getValue())); + } + else if (variant.isEmpty()) { + aasMultiProp.setValue(new ArrayList<>()); + } + } + + + private static void setBlobValue(SubmodelElement submodelElement, Variant variant) { + Blob aasBlob = (Blob) submodelElement; + ByteString bs = null; + if (variant.getValue() != null) { + bs = (ByteString) variant.getValue(); + } + aasBlob.setValue(ByteString.asByteArray(bs)); + } + } diff --git a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/creator/PropertyCreator.java b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/creator/PropertyCreator.java index e66179b2d..f99531139 100644 --- a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/creator/PropertyCreator.java +++ b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/creator/PropertyCreator.java @@ -128,7 +128,7 @@ private static void addOpcUaProperty(Property aasProperty, Submodel submodel, AA LocalizedText displayName = LocalizedText.english(AASPropertyType.VALUE); nodeManager.addSubmodelElementAasMap(myPropertyId, new SubmodelElementData(aasProperty, submodel, SubmodelElementData.Type.PROPERTY_VALUE, propRef)); - LOGGER.debug("setPropertyValueAndType: NodeId {}; Property: {}", myPropertyId, aasProperty); + LOGGER.debug("addOpcUaProperty: NodeId {}; Property: {}", myPropertyId, aasProperty); AasSubmodelElementHelper.setPropertyValueAndType(aasProperty, prop, new ValueData(myPropertyId, browseName, displayName, nodeManager)); } diff --git a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/AasSubmodelElementHelper.java b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/AasSubmodelElementHelper.java index 8881ce995..c53e702ad 100644 --- a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/AasSubmodelElementHelper.java +++ b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/AasSubmodelElementHelper.java @@ -24,7 +24,10 @@ import com.prosysopc.ua.stack.builtintypes.DateTime; import com.prosysopc.ua.stack.builtintypes.LocalizedText; import com.prosysopc.ua.stack.builtintypes.NodeId; +import com.prosysopc.ua.stack.builtintypes.UnsignedByte; import com.prosysopc.ua.stack.builtintypes.UnsignedInteger; +import com.prosysopc.ua.stack.builtintypes.UnsignedLong; +import com.prosysopc.ua.stack.builtintypes.UnsignedShort; import com.prosysopc.ua.stack.core.AccessLevelType; import com.prosysopc.ua.stack.core.Identifiers; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.AasServiceNodeManager; @@ -47,8 +50,8 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.value.TypedValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.TypedValueFactory; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; -import de.fraunhofer.iosb.ilt.faaast.service.model.value.primitive.DateTimeValue; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -231,6 +234,7 @@ public static void addMultiLanguageValueNode(UaNode node, int arraySize, NodeMan public static void setPropertyValueAndType(Property aasProperty, AASPropertyType prop, ValueData valueData) throws StatusException { try { + LOG.atTrace().log("setPropertyValueAndType: {}", aasProperty.getIdShort()); AASDataTypeDefXsd valueDataType; PropertyValue typedValue = ElementValueMapper.toValue(aasProperty, PropertyValue.class); if ((typedValue != null) && (typedValue.getValue() != null)) { @@ -255,20 +259,34 @@ public static void setPropertyValueAndType(Property aasProperty, AASPropertyType setInt32PropertyValue(valueData, typedValue, prop); break; + case UnsignedInt: + setUInt32PropertyValue(valueData, typedValue, prop); + break; + case Long: - case Integer: - case Decimal: setInt64PropertyValue(valueData, typedValue, prop); break; + case UnsignedLong: + setUInt64PropertyValue(valueData, typedValue, prop); + break; + case Short: setInt16PropertyValue(valueData, typedValue, prop); break; + case UnsignedShort: + setUInt16PropertyValue(valueData, typedValue, prop); + break; + case Byte: setSBytePropertyValue(valueData, typedValue, prop); break; + case UnsignedByte: + setBytePropertyValue(valueData, typedValue, prop); + break; + case Double: setDoublePropertyValue(valueData, typedValue, prop); break; @@ -277,10 +295,15 @@ public static void setPropertyValueAndType(Property aasProperty, AASPropertyType setFloatPropertyValue(valueData, typedValue, prop); break; - case String: + case String, AnyUri, Time, Duration, GDay, GMonth, GMonthDay, GYear, GYearMonth, Decimal, Integer, PositiveInteger, NonPositiveInteger, NegativeInteger, + NonNegativeInteger, Date: setStringValue(valueData, typedValue, prop); break; + case Base64Binary, HexBinary: + setByteStringPropertyValue(valueData, typedValue, prop); + break; + default: LOG.warn("setPropertyValueAndType: Property {}: Unknown type: {}; use string as default", prop.getBrowseName().getName(), aasProperty.getValueType()); PlainProperty myDefaultProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), @@ -315,21 +338,41 @@ private static void setInt16PropertyValue(ValueData valueData, PropertyValue typ } + private static void setUInt16PropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { + prop.addProperty(createUInt16Property(valueData, typedValue != null ? typedValue.getValue() : null)); + } + + private static void setInt32PropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { prop.addProperty(createInt32Property(valueData, typedValue != null ? typedValue.getValue() : null)); } + private static void setUInt32PropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { + prop.addProperty(createUInt32Property(valueData, typedValue != null ? typedValue.getValue() : null)); + } + + private static void setInt64PropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { prop.addProperty(createInt64Property(valueData, typedValue != null ? typedValue.getValue() : null)); } + private static void setUInt64PropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { + prop.addProperty(createUInt64Property(valueData, typedValue != null ? typedValue.getValue() : null)); + } + + private static void setSBytePropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { prop.addProperty(createSByteProperty(valueData, typedValue != null ? typedValue.getValue() : null)); } + private static void setBytePropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { + prop.addProperty(createByteProperty(valueData, typedValue != null ? typedValue.getValue() : null)); + } + + private static void setStringValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { prop.addProperty(UaHelper.createStringProperty(valueData, typedValue != null ? typedValue.getValue() : null)); } @@ -378,6 +421,17 @@ private static PlainProperty createSByteProperty(ValueData valueData, Type } + private static PlainProperty createByteProperty(ValueData valueData, TypedValue typedValue) throws StatusException { + PlainProperty usbyteProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); + usbyteProperty.setDataTypeId(Identifiers.Byte); + usbyteProperty.setDescription(new LocalizedText("", "")); + if ((typedValue != null) && (typedValue.getValue() != null)) { + usbyteProperty.setValue(typedValue.getValue()); + } + return usbyteProperty; + } + + private static PlainProperty createInt16Property(ValueData valueData, TypedValue typedValue) throws StatusException { PlainProperty int16Property = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); int16Property.setDataTypeId(Identifiers.Int16); @@ -389,6 +443,17 @@ private static PlainProperty createInt16Property(ValueData valueData, Typ } + private static PlainProperty createUInt16Property(ValueData valueData, TypedValue typedValue) throws StatusException { + PlainProperty uint16Property = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); + uint16Property.setDataTypeId(Identifiers.UInt16); + uint16Property.setDescription(new LocalizedText("", "")); + if ((typedValue != null) && (typedValue.getValue() != null)) { + uint16Property.setValue(typedValue.getValue()); + } + return uint16Property; + } + + private static PlainProperty createInt64Property(ValueData valueData, TypedValue typedValue) throws NumberFormatException, StatusException { PlainProperty longProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); longProperty.setDataTypeId(Identifiers.Int64); @@ -404,6 +469,21 @@ private static PlainProperty createInt64Property(ValueData valueData, Type } + private static PlainProperty createUInt64Property(ValueData valueData, TypedValue typedValue) throws NumberFormatException, StatusException { + PlainProperty ulongProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); + ulongProperty.setDataTypeId(Identifiers.UInt64); + ulongProperty.setDescription(new LocalizedText("", "")); + if (typedValue != null) { + Object obj = typedValue.getValue(); + if ((obj != null) && (!(obj instanceof UnsignedLong))) { + obj = UnsignedLong.valueOf(obj.toString()); + } + ulongProperty.setValue(obj); + } + return ulongProperty; + } + + private static PlainProperty createInt32Property(ValueData valueData, TypedValue typedValue) throws StatusException { PlainProperty intProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); intProperty.setDataTypeId(Identifiers.Int32); @@ -415,13 +495,24 @@ private static PlainProperty createInt32Property(ValueData valueData, T } + private static PlainProperty createUInt32Property(ValueData valueData, TypedValue typedValue) throws StatusException { + PlainProperty uintProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); + uintProperty.setDataTypeId(Identifiers.UInt32); + uintProperty.setDescription(new LocalizedText("", "")); + if ((typedValue != null) && (typedValue.getValue() != null)) { + uintProperty.setValue(typedValue.getValue()); + } + return uintProperty; + } + + private static PlainProperty createDateTimeProperty(ValueData valueData, TypedValue typedValue) throws StatusException { PlainProperty dateTimeProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), valueData.getDisplayName()); dateTimeProperty.setDataTypeId(Identifiers.DateTime); dateTimeProperty.setDescription(new LocalizedText("", "")); if ((typedValue != null) && (typedValue.getValue() != null)) { - if (typedValue instanceof DateTimeValue dtval) { - dateTimeProperty.setValue(ValueConverter.createDateTime(dtval.getValue())); + if (typedValue.getValue() instanceof OffsetDateTime odt) { + dateTimeProperty.setValue(ValueConverter.createDateTime(odt)); } else { dateTimeProperty.setValue(typedValue.getValue()); @@ -431,20 +522,43 @@ private static PlainProperty createDateTimeProperty(ValueData valueDat } + private static void setByteStringPropertyValue(ValueData valueData, PropertyValue typedValue, AASPropertyType prop) throws StatusException { + prop.addProperty(createByteStringProperty(valueData, typedValue != null ? typedValue.getValue() : null)); + } + + + private static void setByteStringRangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, + TypedValue maxTypedValue) + throws StatusException { + if (minValue != null) { + range.addProperty(createByteStringProperty(minData, minTypedValue)); + } + + if (maxValue != null) { + range.addProperty(createByteStringProperty(maxData, maxTypedValue)); + } + } + + + private static PlainProperty createByteStringProperty(ValueData valueData, TypedValue typedValue) throws StatusException { + PlainProperty byteStringProperty = new PlainProperty<>(valueData.getNodeManager(), valueData.getNodeId(), valueData.getBrowseName(), + valueData.getDisplayName()); + byteStringProperty.setDataTypeId(Identifiers.ByteString); + byteStringProperty.setDescription(new LocalizedText("", "")); + if ((typedValue != null) && (typedValue.getValue() != null)) { + byteStringProperty.setValue(typedValue.getValue()); + } + return byteStringProperty; + } + + public static void setRangeValueAndType(DataTypeDefXsd valueType, String minValue, String maxValue, AASRangeType range, ValueData minData, ValueData maxData) throws StatusException { try { TypedValue minTypedValue = TypedValueFactory.create(valueType, minValue); TypedValue maxTypedValue = TypedValueFactory.create(valueType, maxValue); - AASDataTypeDefXsd valueDataType; - if (minTypedValue != null) { - valueDataType = ValueConverter.datatypeToOpcDataType(minTypedValue.getDataType()); - } - else { - valueDataType = ValueConverter.convertDataTypeDefXsd(valueType); - } - + AASDataTypeDefXsd valueDataType = getValueType(minTypedValue, valueType); range.setValueType(valueDataType); switch (valueDataType) { @@ -460,20 +574,34 @@ public static void setRangeValueAndType(DataTypeDefXsd valueType, String minValu setInt32RangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; + case UnsignedInt: + setUInt32RangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); + break; + case Long: - case Integer: - case Decimal: setInt64RangeValues(minValue, minData, minTypedValue, maxValue, maxData, maxTypedValue, range); break; + case UnsignedLong: + setUInt64RangeValues(minValue, minData, minTypedValue, maxValue, maxData, maxTypedValue, range); + break; + case Short: setInt16RangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; + case UnsignedShort: + setUInt16RangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); + break; + case Byte: setSByteRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; + case UnsignedByte: + setByteRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); + break; + case Double: setDoubleRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; @@ -482,25 +610,36 @@ public static void setRangeValueAndType(DataTypeDefXsd valueType, String minValu setFloatRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; - case String: + case String, AnyUri, Time, Duration, GDay, GMonth, GMonthDay, GYear, GYearMonth, Decimal, Integer, PositiveInteger, NonPositiveInteger, NegativeInteger, + NonNegativeInteger, Date: setStringRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; + case Base64Binary, HexBinary: + setByteStringRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); + break; + default: LOG.warn("setRangeValueAndType: Range {}: Unknown type: {}; use string as default", range.getBrowseName().getName(), valueType); - if (minValue != null) { - range.addProperty(UaHelper.createStringProperty(minData, minTypedValue)); - } - - if (maxValue != null) { - range.addProperty(UaHelper.createStringProperty(maxData, maxTypedValue)); - } + setStringRangeValues(minValue, minData, minTypedValue, range, maxValue, maxData, maxTypedValue); break; } } catch (Exception ex) { - LOG.error("setPropertyValueAndType Exception", ex); + LOG.error("setRangeValueAndType Exception", ex); + } + } + + + private static AASDataTypeDefXsd getValueType(TypedValue typedValue, DataTypeDefXsd valueType) { + AASDataTypeDefXsd valueDataType; + if (typedValue != null) { + valueDataType = ValueConverter.datatypeToOpcDataType(typedValue.getDataType()); + } + else { + valueDataType = ValueConverter.convertDataTypeDefXsd(valueType); } + return valueDataType; } @@ -556,6 +695,19 @@ private static void setSByteRangeValues(String minValue, ValueData minData, Type } + private static void setByteRangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, + TypedValue maxTypedValue) + throws StatusException { + if (minValue != null) { + range.addProperty(createByteProperty(minData, minTypedValue)); + } + + if (maxValue != null) { + range.addProperty(createByteProperty(maxData, maxTypedValue)); + } + } + + private static void setInt16RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, TypedValue maxTypedValue) throws StatusException { @@ -569,6 +721,19 @@ private static void setInt16RangeValues(String minValue, ValueData minData, Type } + private static void setUInt16RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, + TypedValue maxTypedValue) + throws StatusException { + if (minValue != null) { + range.addProperty(createUInt16Property(minData, minTypedValue)); + } + + if (maxValue != null) { + range.addProperty(createUInt16Property(maxData, maxTypedValue)); + } + } + + private static void setInt64RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, String maxValue, ValueData maxData, TypedValue maxTypedValue, AASRangeType range) throws NumberFormatException, StatusException { @@ -581,6 +746,18 @@ private static void setInt64RangeValues(String minValue, ValueData minData, Type } + private static void setUInt64RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, String maxValue, ValueData maxData, TypedValue maxTypedValue, + AASRangeType range) + throws NumberFormatException, StatusException { + if (minValue != null) { + range.addProperty(createUInt64Property(minData, minTypedValue)); + } + if (maxValue != null) { + range.addProperty(createUInt64Property(maxData, maxTypedValue)); + } + } + + private static void setInt32RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, TypedValue maxTypedValue) throws StatusException { @@ -594,6 +771,19 @@ private static void setInt32RangeValues(String minValue, ValueData minData, Type } + private static void setUInt32RangeValues(String minValue, ValueData minData, TypedValue minTypedValue, AASRangeType range, String maxValue, ValueData maxData, + TypedValue maxTypedValue) + throws StatusException { + if (minValue != null) { + range.addProperty(createUInt32Property(minData, minTypedValue)); + } + + if (maxValue != null) { + range.addProperty(createUInt32Property(maxData, maxTypedValue)); + } + } + + private static void setDateTimeRangeValues(String minValue, ValueData minData, TypedValue minTypedValue, String maxValue, ValueData maxData, TypedValue maxTypedValue, AASRangeType range) throws StatusException { diff --git a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/UaHelper.java b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/UaHelper.java index 3931de8a2..6f0238a58 100644 --- a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/UaHelper.java +++ b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/UaHelper.java @@ -65,7 +65,7 @@ public static PlainProperty createStringProperty(ValueData valueData, Ty stringProperty.setDataTypeId(Identifiers.String); stringProperty.setDescription(new LocalizedText("", "")); if ((typedValue != null) && (typedValue.getValue() != null)) { - stringProperty.setValue(typedValue.getValue()); + stringProperty.setValue(typedValue.asString()); } return stringProperty; } diff --git a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/listener/AasServiceIoManagerListener.java b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/listener/AasServiceIoManagerListener.java index 2740dd5e6..0b36fcf1e 100644 --- a/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/listener/AasServiceIoManagerListener.java +++ b/endpoint/opcua/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/listener/AasServiceIoManagerListener.java @@ -40,6 +40,7 @@ import de.fraunhofer.iosb.ilt.faaast.service.model.value.PropertyValue; import de.fraunhofer.iosb.ilt.faaast.service.model.value.mapper.ElementValueMapper; import de.fraunhofer.iosb.ilt.faaast.service.util.Ensure; +import de.fraunhofer.iosb.ilt.faaast.service.util.ReferenceHelper; import org.eclipse.digitaltwin.aas4j.v3.model.Property; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.slf4j.Logger; @@ -156,15 +157,7 @@ public boolean onWriteValue(ServiceContext sc, NodeId nodeId, UaValueNode uvn, N boolean rv; SubmodelElementData data = nodeManager.getAasData(nodeId); if (data != null) { - if (data.getType() == null) { - LOGGER.warn("onWriteValue: Node {}: unkown type", nodeId); - rv = false; - } - else { - ValueConverter.setSubmodelElementValue(data, dv); - - rv = endpoint.writeValue(data.getSubmodelElement(), data.getSubmodel(), data.getReference()); - } + rv = doWriteValue(data, nodeId, dv); } else { LOGGER.warn("onWriteValue: Node {}: SubmodelElementData not found", nodeId); @@ -180,6 +173,9 @@ public boolean onWriteValue(ServiceContext sc, NodeId nodeId, UaValueNode uvn, N } } } + catch (StatusException se) { + throw se; + } catch (Exception ex) { throw new StatusException(ex.getMessage(), StatusCodes.Bad_UnexpectedError); } @@ -189,4 +185,25 @@ public boolean onWriteValue(ServiceContext sc, NodeId nodeId, UaValueNode uvn, N return true; } + + private boolean doWriteValue(SubmodelElementData data, NodeId nodeId, DataValue dv) throws StatusException { + boolean rv; + if (data.getType() == null) { + LOGGER.warn("doWriteValue: Node {}: unkown type", nodeId); + rv = false; + } + else { + if (ValueConverter.setSubmodelElementValue(data, dv)) { + rv = endpoint.writeValue(data.getSubmodelElement(), data.getSubmodel(), data.getReference()); + } + else { + LOGGER.atWarn().log("doWriteValue: SubmodelElement '{}': invalid Value {}", ReferenceHelper.toString(data.getReference()), dv.getValue()); + // The value is not valid for the corresponding datatype + // possible error values are: BadOutOfRange or BadTypeMismatch + throw new StatusException(StatusCodes.Bad_OutOfRange); + } + } + return rv; + } + } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullTest.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullTest.java index 48fd3e72f..f87275665 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullTest.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointFullTest.java @@ -43,8 +43,13 @@ import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.TestUtils; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection.TestAssetConnectionConfig; import de.fraunhofer.iosb.ilt.faaast.service.endpoint.opcua.helper.assetconnection.TestOperationProviderConfig; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.Response; +import de.fraunhofer.iosb.ilt.faaast.service.model.api.request.SetSubmodelElementValueByPathRequest; +import de.fraunhofer.iosb.ilt.faaast.service.model.exception.ValueFormatException; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementCreateEventMessage; import de.fraunhofer.iosb.ilt.faaast.service.model.messagebus.event.change.ElementDeleteEventMessage; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.Datatype; +import de.fraunhofer.iosb.ilt.faaast.service.model.value.PropertyValue; import de.fraunhofer.iosb.ilt.faaast.service.util.PortHelper; import java.io.IOException; import java.time.OffsetDateTime; @@ -887,6 +892,57 @@ public void testSubmodelElementList() throws ServiceException, SecureIdentityExc } + @Test + public void testUpdatePropertyValue() throws SecureIdentityException, ServiceException, IOException, StatusException, ValueFormatException { + UaClient client = new UaClient(ENDPOINT_URL); + client.setSecurityMode(SecurityMode.NONE); + TestUtils.initialize(client); + client.connect(); + System.out.println("testUpdatePropertyValue: client connected"); + + aasns = client.getAddressSpace().getNamespaceTable().getIndex(VariableIds.AASAssetAdministrationShellType_AssetInformation_AssetKind.getNamespaceUri()); + + List relPath = new ArrayList<>(); + List browsePath = new ArrayList<>(); + browsePath.add(new RelativePathElement(Identifiers.HierarchicalReferences, false, true, new QualifiedName(aasns, TestConstants.AAS_ENVIRONMENT_NAME))); + browsePath.add(new RelativePathElement(Identifiers.HierarchicalReferences, false, true, new QualifiedName(aasns, TestConstants.FULL_SUBMODEL_6_NAME))); + browsePath.add(new RelativePathElement(Identifiers.HierarchicalReferences, false, true, new QualifiedName(aasns, TestConstants.FULL_INT64_PROP_NAME))); + browsePath.add(new RelativePathElement(Identifiers.HasProperty, false, true, new QualifiedName(aasns, TestConstants.PROPERTY_VALUE_NAME))); + relPath.add(new RelativePath(browsePath.toArray(RelativePathElement[]::new))); + + BrowsePathResult[] bpres = client.getAddressSpace().translateBrowsePathsToNodeIds(Identifiers.ObjectsFolder, relPath.toArray(RelativePath[]::new)); + Assert.assertNotNull("testWriteProperty Browse Result Null", bpres); + Assert.assertEquals("testWriteProperty Browse Result: size doesn't match", 1, bpres.length); + Assert.assertTrue("testWriteProperty Browse Result Good", bpres[0].getStatusCode().isGood()); + + BrowsePathTarget[] targets = bpres[0].getTargets(); + Assert.assertNotNull("testWriteProperty ValueType Null", targets); + Assert.assertTrue("testWriteProperty ValueType empty", targets.length > 0); + DataValue value = client.readValue(targets[0].getTargetId()); + Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + Long oldValue = Long.MAX_VALUE; + Assert.assertEquals("intial value not equal", oldValue, value.getValue().getValue()); + + Long newValue = Long.valueOf(159785); + + // set new value in service + SetSubmodelElementValueByPathRequest request = SetSubmodelElementValueByPathRequest.builder().submodelId(TestConstants.FULL_SUBMODEL_6_ID) + .path(TestConstants.FULL_INT64_PROP_NAME) + .value(PropertyValue.of(Datatype.LONG, newValue.toString())) + .build(); + Response response = service.execute(request); + Assert.assertEquals(de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode.SUCCESS_NO_CONTENT, response.getStatusCode()); + + // read new value + value = client.readValue(targets[0].getTargetId()); + Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + Assert.assertEquals("new value not equal", newValue, value.getValue().getValue()); + + System.out.println("disconnect client"); + client.disconnect(); + } + + private void testSubmodel1(UaClient client, NodeId submodelNode) throws ServiceException, AddressSpaceException, ServiceResultException, StatusException { TestUtils.checkDisplayName(client, submodelNode, "Submodel:" + TestConstants.FULL_SUBMODEL_1_NAME); TestUtils.checkType(client, submodelNode, new NodeId(aasns, TestConstants.AAS_SUBMODEL_TYPE_ID)); diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointTest.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointTest.java index 2e9898076..39b3ac2cd 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointTest.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/OpcUaEndpointTest.java @@ -239,15 +239,15 @@ public void testUpdatePropertyValue() throws SecureIdentityException, ServiceExc Assert.assertTrue("testWriteProperty ValueType empty", targets.length > 0); DataValue value = client.readValue(targets[0].getTargetId()); Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); - Long oldValue = Long.valueOf(4370); + String oldValue = "4370"; Assert.assertEquals("intial value not equal", oldValue, value.getValue().getValue()); - Long newValue = Long.valueOf(9999); + String newValue = "9999"; // set new value in service SetSubmodelElementValueByPathRequest request = SetSubmodelElementValueByPathRequest.builder().submodelId(TestConstants.SUBMODEL_OPER_DATA_NAME) .path(TestConstants.ROTATION_SPEED_NAME) - .value(PropertyValue.of(Datatype.INTEGER, newValue.toString())) + .value(PropertyValue.of(Datatype.INTEGER, newValue)) .build(); Response response = service.execute(request); Assert.assertEquals(de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode.SUCCESS_NO_CONTENT, response.getStatusCode()); @@ -325,12 +325,12 @@ public void testPropertyChange() throws SecureIdentityException, IOException, Se Assert.assertNotNull("testPropertyChangeFromMessageBus ValueType Null", targets); Assert.assertTrue("testPropertyChangeFromMessageBus ValueType empty", targets.length > 0); - Long newValue = Long.valueOf(5005); + String newValue = "5005"; // set new value in service SetSubmodelElementValueByPathRequest request = SetSubmodelElementValueByPathRequest.builder().submodelId(TestConstants.SUBMODEL_TECH_DATA_NAME) .path(TestConstants.MAX_ROTATION_SPEED_NAME) - .value(PropertyValue.of(Datatype.INTEGER, newValue.toString())) + .value(PropertyValue.of(Datatype.INTEGER, newValue)) .build(); Response response = service.execute(request); Assert.assertEquals(de.fraunhofer.iosb.ilt.faaast.service.model.api.StatusCode.SUCCESS_NO_CONTENT, response.getStatusCode()); @@ -842,7 +842,7 @@ private void testSubmodelOperationalData(UaClient client, NodeId submodelNode) t TestUtils.checkEmbeddedDataSpecificationNode(client, submodelNode, aasns); TestUtils.checkQualifierNode(client, submodelNode, aasns, new ArrayList<>()); TestUtils.checkAasPropertyObject(client, submodelNode, aasns, TestConstants.ROTATION_SPEED_NAME, "VARIABLE", AASDataTypeDefXsd.Integer, - Long.valueOf(4370), new ArrayList<>()); + "4370", new ArrayList<>()); } @@ -858,9 +858,9 @@ private void testSubmodelTechnicalData(UaClient client, NodeId submodelNode) thr TestUtils.checkEmbeddedDataSpecificationNode(client, submodelNode, aasns); TestUtils.checkQualifierNode(client, submodelNode, aasns, new ArrayList<>()); TestUtils.checkAasPropertyObject(client, submodelNode, aasns, TestConstants.MAX_ROTATION_SPEED_NAME, "PARAMETER", - AASDataTypeDefXsd.Integer, Long.valueOf(5000), new ArrayList<>()); + AASDataTypeDefXsd.Integer, "5000", new ArrayList<>()); TestUtils.checkAasPropertyObject(client, submodelNode, aasns, TestConstants.DECIMAL_PROPERTY, "PARAMETER", - AASDataTypeDefXsd.Decimal, Long.valueOf(123456), new ArrayList<>()); + AASDataTypeDefXsd.Decimal, "123456", new ArrayList<>()); } diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestConstants.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestConstants.java index 540f8a18a..33cf77c6e 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestConstants.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestConstants.java @@ -54,6 +54,7 @@ public class TestConstants { public static final String FULL_SUBMODEL_4_NAME = "Test_Submodel_Mandatory"; public static final String FULL_SUBMODEL_5_NAME = "Test_Submodel2_Mandatory"; public static final String FULL_SUBMODEL_6_NAME = "TestSubmodel6"; + public static final String FULL_SUBMODEL_6_ID = "https://acplt.org/Test_Submodel_Missing"; public static final String FULL_SUBMODEL_7_NAME = "TestSubmodelTemplate"; public static final String FULL_REL_ELEMENT_NAME = "ExampleRelationshipElement"; public static final String FULL_SM_ELEM_COLL_UO_NAME = "ExampleSubmodelElementListUnordered"; @@ -65,6 +66,7 @@ public class TestConstants { public static final String FULL_ENTITY2_NAME = "ExampleEntity2"; public static final String FULL_CAPABILITY_NAME = "ExampleCapability"; public static final String FULL_DATETIME_PROP_NAME = "DateTimeProperty"; + public static final String FULL_INT64_PROP_NAME = "Int64Property"; public static final String KIND_NAME = "Kind"; public static final String CATEGORY_NAME = "Category"; diff --git a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestUtils.java b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestUtils.java index c34b3e1e1..84a4701ca 100644 --- a/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestUtils.java +++ b/endpoint/opcua/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/endpoint/opcua/helper/TestUtils.java @@ -45,10 +45,13 @@ import java.io.IOException; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import opc.i4aas.datatypes.AASAssetKindDataType; import opc.i4aas.datatypes.AASDataTypeDefXsd; import opc.i4aas.datatypes.AASKeyDataType; @@ -58,6 +61,8 @@ import opc.i4aas.objecttypes.AASSpecificAssetIdType; import org.eclipse.digitaltwin.aas4j.v3.model.Qualifier; import org.junit.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -65,6 +70,9 @@ */ public class TestUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(TestUtils.class); + private static final long DEFAULT_TIMEOUT = 100; + public static void initialize(UaClient client) throws SecureIdentityException, IOException, UnknownHostException { ApplicationDescription appDescription = new ApplicationDescription(); appDescription.setApplicationName(new LocalizedText("AAS UnitTest Client", Locale.ENGLISH)); @@ -578,6 +586,14 @@ public static void writeNewValueIntern(UaClient client, NodeId writeNode, Object // read new value value = client.readValue(writeNode); Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + if (oldValue == value.getValue().getValue()) { + // if we read the old value again, we try again after a short waiting time + LOGGER.atTrace().log("writeNewValueIntern: read failed, try again after a short waiting time"); + CountDownLatch condition = new CountDownLatch(1); + condition.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + value = client.readValue(writeNode); + Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + } Assert.assertEquals("new value not equal", newValue, value.getValue().getValue()); } @@ -593,6 +609,15 @@ public static void writeNewValueArray(UaClient client, NodeId writeNode, Localiz // read new value value = client.readValue(writeNode); Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + if (Arrays.equals(oldValue, (LocalizedText[]) value.getValue().getValue())) { + // if we read the old value again, we try again after a short waiting time + LOGGER.atTrace().log("writeNewValueArray: read failed, try again after a short waiting time"); + CountDownLatch condition = new CountDownLatch(1); + condition.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + value = client.readValue(writeNode); + Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + } + Assert.assertArrayEquals("new value not equal", newValue, (LocalizedText[]) value.getValue().getValue()); } @@ -608,6 +633,14 @@ public static void writeNewValueArray(UaClient client, NodeId writeNode, AASKeyD // read new value value = client.readValue(writeNode); Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + if (Arrays.equals(oldValue, (AASKeyDataType[]) value.getValue().getValue())) { + // if we read the old value again, we try again after a short waiting time + LOGGER.atTrace().log("writeNewValueArray: read failed, try again after a short waiting time"); + CountDownLatch condition = new CountDownLatch(1); + condition.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + value = client.readValue(writeNode); + Assert.assertEquals(StatusCode.GOOD, value.getStatusCode()); + } Assert.assertArrayEquals("new value not equal", newValue, (AASKeyDataType[]) value.getValue().getValue()); } diff --git a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValue.java b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValue.java index 9c7f7f002..01266095c 100644 --- a/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValue.java +++ b/model/src/main/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValue.java @@ -39,7 +39,7 @@ public TimeValue(OffsetTime value) { @Override public Datatype getDataType() { - return Datatype.DATE; + return Datatype.TIME; } diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/AASFull.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/AASFull.java index 6b9492bf9..671151614 100644 --- a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/AASFull.java +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/AASFull.java @@ -1314,6 +1314,12 @@ public static Submodel createSubmodel6() { .value("2022-07-08T10:22:04") .valueType(DataTypeDefXsd.DATE_TIME) .build()) + .submodelElements(new DefaultProperty.Builder() + .idShort("Int64Property") + .category("Parameter") + .value(Long.toString(Long.MAX_VALUE)) + .valueType(DataTypeDefXsd.LONG) + .build()) .build(); } diff --git a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValueTest.java b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValueTest.java index 54f560367..9bcf361a7 100644 --- a/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValueTest.java +++ b/model/src/test/java/de/fraunhofer/iosb/ilt/faaast/service/model/value/primitive/TimeValueTest.java @@ -33,6 +33,7 @@ public void testTimeOnly() throws ValueFormatException { TypedValue actual = TypedValueFactory.create(Datatype.TIME, value); Assert.assertEquals(expected, actual.getValue()); Assert.assertEquals(value, actual.asString()); + Assert.assertEquals(Datatype.TIME, actual.getDataType()); }