diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 9f6e4ef76..ebb0cf6e5 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -348,13 +348,14 @@ export class AssetInterfaceDescriptionUtil { form.href = v.value; } } else if (typeof v.idShort === "string" && v.idShort.length > 0) { - // TODO is this still relevant? // pick *any* value (and possibly override, e.g. contentType) - // TODO Should we add all value's (e.g., dataMapping might be empty array) ? - // if (typeof v.value === "string" ||typeof v.value === "number" || typeof v.value === "boolean") { if (v.value != null) { - form[v.idShort] = v.value; + // Note: AID does not allow idShort to contain values with colon (i.e., ":") --> "_" used instead + // --> THIS MAY LEAD TO PROBLEMS BUT THAT'S HOW IT IS SPECIFIED + const tdTerm = (v.idShort as string).replace("_", ":"); + form[tdTerm] = v.value; // use valueType to convert the string value + // TODO Should we add/support all value's (e.g., dataMapping might be empty array) ? if ( v.valueType != null && v.valueType.dataObjectType != null && @@ -364,7 +365,7 @@ export class AssetInterfaceDescriptionUtil { // XSD schemaTypes, https://www.w3.org/TR/xmlschema-2/#built-in-datatypes switch (v.valueType.dataObjectType.name) { case "boolean": - form[v.idShort] = form[v.idShort] === "true"; + form[tdTerm] = form[v.value] === "true"; break; case "float": case "double": @@ -382,7 +383,7 @@ export class AssetInterfaceDescriptionUtil { case "unsignedShort": case "unsignedByte": case "positiveInteger": - form[v.idShort] = Number(form[v.idShort]); + form[tdTerm] = Number(form[v.value]); break; // TODO handle more XSD types ? } @@ -836,8 +837,13 @@ export class AssetInterfaceDescriptionUtil { // --> pick the first one that matches protocol (other means in future?) // walk over string values like: "href", "contentType", "htv:methodName", ... - for (const formTerm in formElementPicked) { + for (let formTerm in formElementPicked) { const formValue = formElementPicked[formTerm]; + + // Note: AID does not allow idShort to contain values with colon (i.e., ":") --> "_" used instead + // TODO are there more characters we need to deal with? + formTerm = formTerm.replace(":", "_"); + if (typeof formValue === "string") { propertyForm.push({ idShort: formTerm, diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index f411ffb64..0a4b4e282 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -210,6 +210,157 @@ class AssetInterfaceDescriptionUtilTest { expect(tdObj.properties.device_name.forms[0]).not.to.have.property("security"); } + @test async "should correctly roundtrip inverterModbus from/to AID"() { + const aasInput = (await fs.readFile("test/util/inverterModbus.json")).toString(); + const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(aasInput); + + const aidOutput = this.assetInterfaceDescriptionUtil.transformTD2SM(td); + + const smObj = JSON.parse(aidOutput); + expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); + expect(smObj).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf.greaterThan(0); + const smInterface = smObj.submodelElements[0]; + expect(smInterface).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); + let hasThingTitle = false; + let hasEndpointMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "title") { + hasThingTitle = true; + expect(smValue).to.have.property("value").to.equal("Inverter GEN44"); + } else if (smValue.idShort === "EndpointMetadata") { + hasEndpointMetadata = true; + const endpointMetadata = smValue; + expect(endpointMetadata).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); + let hasSecurity = false; + let hasSecurityDefinitions = false; + for (const endpointMetadataValue of endpointMetadata.value) { + if (endpointMetadataValue.idShort === "security") { + hasSecurity = true; + expect(endpointMetadataValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + expect(endpointMetadataValue.value[0].value).to.equal("nosec_sc"); + } else if (endpointMetadataValue.idShort === "securityDefinitions") { + hasSecurityDefinitions = true; + expect(endpointMetadataValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasBasicSC = false; + for (const securityDefinitionValue of endpointMetadataValue.value) { + if (securityDefinitionValue.idShort === "nosec_sc") { + hasBasicSC = true; + expect(securityDefinitionValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasBasic = false; + for (const sec of securityDefinitionValue.value) { + if (sec.idShort === "scheme") { + hasBasic = true; + expect(sec.value).to.equal("nosec"); + } + } + expect(hasBasic).to.equal(true); + } + } + expect(hasBasicSC).to.equal(true); + } + } + expect(hasSecurity).to.equal(true); + expect(hasSecurityDefinitions).to.equal(true); + } + } + expect(hasThingTitle, "No thing title").to.equal(true); + expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); + + // InterfaceMetadata with properties etc + let hasInterfaceMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "InterfaceMetadata") { + hasInterfaceMetadata = true; + expect(smValue).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); + let hasProperties = false; + for (const interactionValues of smValue.value) { + if (interactionValues.idShort === "properties") { + hasProperties = true; + expect(interactionValues) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasPropertyDeviceName = false; + for (const propertyValue of interactionValues.value) { + if (propertyValue.idShort === "device_name") { + hasPropertyDeviceName = true; + expect(propertyValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasType = false; + let hasTitle = false; + let hasForms = false; + for (const propProperty of propertyValue.value) { + if (propProperty.idShort === "type") { + hasType = true; + expect(propProperty.value).to.equal("string"); + } else if (propProperty.idShort === "title") { + hasTitle = true; + expect(propProperty.value).to.equal("Device name"); + } else if (propProperty.idShort === "forms") { + hasForms = true; + expect(propProperty) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasHref = false; + let hasOp = false; + let hasContentType = false; + let hasModbusFunction = false; + let hasModbusType = false; + for (const formEntry of propProperty.value) { + if (formEntry.idShort === "href") { + hasHref = true; + expect(formEntry.value).to.equal( + "modbus+tcp://192.168.178.146:502/1/40020?quantity=16" + ); + } else if (formEntry.idShort === "op") { + hasOp = true; + expect(formEntry.value).to.equal("readproperty"); + } else if (formEntry.idShort === "contentType") { + hasContentType = true; + expect(formEntry.value).to.equal("application/octet-stream"); + } else if (formEntry.idShort === "modbus_function") { + // vs. "modbus:function" + hasModbusFunction = true; + expect(formEntry.value).to.equal("readHoldingRegisters"); + } else if (formEntry.idShort === "modbus_type") { + // vs. "modbus:type" + hasModbusType = true; + expect(formEntry.value).to.equal("string"); + } + } + expect(hasHref).to.equal(true); + expect(hasOp).to.equal(true); + expect(hasContentType).to.equal(true); + expect(hasModbusFunction).to.equal(true); + expect(hasModbusType).to.equal(true); + } + } + expect(hasType).to.equal(true); + expect(hasTitle).to.equal(true); + expect(hasForms).to.equal(true); + } + } + expect(hasPropertyDeviceName).to.equal(true); + } + } + expect(hasProperties).to.equal(true); + } + } + expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); + } + td1Base = "https://www.example.com/"; td1: ThingDescription = { "@context": "https://www.w3.org/2022/wot/td/v1.1", @@ -242,7 +393,7 @@ class AssetInterfaceDescriptionUtilTest { }, }; - @test async "should correctly transform sample TD into JSON submodel"() { + @test async "should correctly transform sample TD into AID submodel"() { const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["https"]); const smObj = JSON.parse(sm); @@ -358,7 +509,6 @@ class AssetInterfaceDescriptionUtilTest { let hasHref = false; let hasContentType = false; let hasHtvMethodName = false; - // let hasOp = false; for (const formEntry of propProperty.value) { if (formEntry.idShort === "href") { hasHref = true; @@ -369,18 +519,14 @@ class AssetInterfaceDescriptionUtilTest { } else if (formEntry.idShort === "contentType") { hasContentType = true; expect(formEntry.value).to.equal("application/json"); - } else if (formEntry.idShort === "htv:methodName") { + } else if (formEntry.idShort === "htv_methodName") { hasHtvMethodName = true; expect(formEntry.value).to.equal("GET"); - // } else if (formEntry.idShort === "op") { - // hasOp = true; - // expect(formEntry.value).to.have.members(["readproperty"]); } } expect(hasHref).to.equal(true); expect(hasContentType).to.equal(true); expect(hasHtvMethodName).to.equal(true); - // expect(hasOp).to.equal(true); } } expect(hasType).to.equal(true); diff --git a/packages/td-tools/test/util/inverterModbus.json b/packages/td-tools/test/util/inverterModbus.json index 09e879a0d..d220784ab 100644 --- a/packages/td-tools/test/util/inverterModbus.json +++ b/packages/td-tools/test/util/inverterModbus.json @@ -117,13 +117,13 @@ "modelType": "Property" }, { - "idShort": "modbus:function", + "idShort": "modbus_function", "valueType": "xs:string", "value": "readHoldingRegisters", "modelType": "Property" }, { - "idShort": "modbus:type", + "idShort": "modbus_type", "valueType": "xs:string", "value": "string", "modelType": "Property"