From d16e650dda243bf8ae0c92f70aee77ee64d32356 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 7 Aug 2023 15:28:44 +0200 Subject: [PATCH 01/30] fix: issue with using wrong arguments --- packages/td-tools/src/util/asset-interface-description.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index d2ae2d06c..f0cca7c5d 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -437,7 +437,7 @@ export class AssetInterfaceDescriptionUtil { /** @deprecated use transformAAS2TD method instead */ public transformToTD(aid: string, template?: string, submodelRegex?: string): string { - return this.transformAAS2TD(aid, submodelRegex); + return this.transformAAS2TD(aid, template, submodelRegex); } /** From 89cb6f3170243502dab2fdc893a89b0d44b12a6b Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 7 Aug 2023 15:33:21 +0200 Subject: [PATCH 02/30] docs: update readme with the preferred way of using the interface --- packages/td-tools/src/util/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/td-tools/src/util/README.md b/packages/td-tools/src/util/README.md index 3cc00a9ce..4d38befe2 100644 --- a/packages/td-tools/src/util/README.md +++ b/packages/td-tools/src/util/README.md @@ -36,16 +36,16 @@ let assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); async function example() { try { - const aid = await fs.readFile("AID_v03_counter.json", { + const aas = await fs.readFile("AID_v03_counter.json", { encoding: "utf8", }); // transform AID to WoT TD - let tdAID = assetInterfaceDescriptionUtil.transformToTD(aid, `{"title": "counter"}`); - // Note: transformToTD() may have up to 3 input parameters - // * aid (required): AID input + let tdAID = assetInterfaceDescriptionUtil.transformAAS2TD(aas, `{"title": "counter"}`); + // Note: transformAAS2TD() may have up to 3 input parameters + // * aas (required): AAS in JSON format // * template (optional): Initial TD template // * submodelRegex (optional): Submodel filter based on regular expression - // e.g., filtering HTTP only by calling transformToTD(aid, `{}`, "HTTP") + // e.g., filtering HTTP only by calling transformAAS2TD(aas, `{}`, "HTTP") // do work as usual const WoT = await servient.start(); From e45f624be4e1afce965bbbd54d7eba04308c357d Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 7 Aug 2023 16:55:13 +0200 Subject: [PATCH 03/30] refactor: initial test (setup) --- .../src/util/asset-interface-description.ts | 21 +++++++++++++++++++ .../test/AssetInterfaceDescriptionTest.ts | 10 +++++++++ 2 files changed, 31 insertions(+) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index f0cca7c5d..88de6dc00 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -16,6 +16,7 @@ import Thing from "../thing-description"; import * as TD from "../thing-description"; import { SecurityScheme } from "wot-thing-description-types"; +import * as TDParser from "../td-parser"; import debug from "debug"; const namespace = "node-wot:td-tools:asset-interface-description-util"; @@ -475,4 +476,24 @@ export class AssetInterfaceDescriptionUtil { return this._transform(smInformation, template); } + + /** + * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format + * + * @param td input AID submodel in JSON format + * @returns transformed AID submodel definition in JSON format + */ + public transformTD2SM(td: string): string { + const thing = TDParser.parseTD(td); + console.log("TD " + thing.title + " parsed..."); + + // TODO + // value entry "idShort": "EndpointMetadata" + // value entry "idShort": "InterfaceMetadata" + const tdObject = { + idShort: thing.title, + }; + + return JSON.stringify(tdObject); + } } diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 042e9517c..5be5b73f8 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -228,4 +228,14 @@ class AssetInterfaceDescriptionUtilTest { const td4Obj = JSON.parse(td4); expect(td4Obj).to.not.have.property("properties"); } + + @test async "should correctly transform sample TD into JSON submodel"() { + const td = `{"title": "testTD"}`; + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(td); + + const smObj = JSON.parse(sm); + expect(smObj).to.have.property("idShort").that.equals("testTD"); + + // TODO EndpointMetadata and InterfaceMetadata + } } From d4243dd26b022b10c228bbde81f1b92e4e439bb5 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Wed, 23 Aug 2023 11:23:44 +0200 Subject: [PATCH 04/30] feat: update transformation to latest AID version Note: examples AID_v03.json and AID_v03_counter.json are no longer compliant -> skipped for now --- .../src/util/asset-interface-description.ts | 163 +++-- .../test/AssetInterfaceDescriptionTest.ts | 139 +++- .../test/util/counterHTTP_minimal.aasx | Bin 0 -> 5714 bytes .../test/util/counterHTTP_minimal.json | 689 ++++++++++++++++++ 4 files changed, 925 insertions(+), 66 deletions(-) create mode 100644 packages/td-tools/test/util/counterHTTP_minimal.aasx create mode 100644 packages/td-tools/test/util/counterHTTP_minimal.json diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 88de6dc00..cb62600af 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -89,18 +89,29 @@ export class AssetInterfaceDescriptionUtil { if (v.idShort === "securityDefinitions") { const securitySchemes: Array = []; if (v.value && v.value instanceof Array) { - for (const secValue of v.value) { - // allow all *other* security schemes like "uasec" as welll - const ss: SecurityScheme = { scheme: secValue.idShort }; - securitySchemes.push(ss); - /* if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { - const ss : SecurityScheme = { scheme: secValue.idShort}; - securitySchemes.push(ss); - } */ - if (secValue.value && secValue.value instanceof Array) { - for (const v of secValue.value) { - if (v.idShort && typeof v.idShort === "string" && v.idShort.length > 0 && v.value) { - ss[v.idShort] = v.value; + for (const securityDefinitionsValue of v.value) { + if (securityDefinitionsValue.idShort === "scheme") { + if (securityDefinitionsValue.value && securityDefinitionsValue.value instanceof Array) { + for (const secValue of securityDefinitionsValue.value) { + // allow all *other* security schemes like "uasec" as well + const ss: SecurityScheme = { scheme: secValue.idShort }; + securitySchemes.push(ss); + /* if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { + const ss : SecurityScheme = { scheme: secValue.idShort}; + securitySchemes.push(ss); + } */ + if (secValue.value && secValue.value instanceof Array) { + for (const v of secValue.value) { + if ( + v.idShort && + typeof v.idShort === "string" && + v.idShort.length > 0 && + v.value + ) { + ss[v.idShort] = v.value; + } + } + } } } } @@ -130,52 +141,59 @@ export class AssetInterfaceDescriptionUtil { } } if (vi.interaction.value instanceof Array) { - for (const v of vi.interaction.value) { - // Binding HTTP - if (v.idShort === "href") { - if (form.href && form.href.length > 0) { - form.href = form.href + v.value; // TODO handle leading/trailing slashes - } else { - form.href = v.value; - } - } else if (typeof v.idShort === "string" && v.idShort.length > 0) { - // 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) { - form[v.idShort] = v.value; - // use valueType to convert the string value - if ( - v.valueType && - v.valueType && - v.valueType.dataObjectType && - v.valueType.dataObjectType.name && - typeof v.valueType.dataObjectType.name === "string" - ) { - // 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"; - break; - case "float": - case "double": - case "decimal": - case "integer": - case "nonPositiveInteger": - case "negativeInteger": - case "long": - case "int": - case "short": - case "byte": - case "nonNegativeInteger": - case "unsignedLong": - case "unsignedInt": - case "unsignedShort": - case "unsignedByte": - case "positiveInteger": - form[v.idShort] = Number(form[v.idShort]); - break; - // TODO handle more XSD types ? + for (const iv of vi.interaction.value) { + if (iv.idShort === "forms") { + if (iv.value instanceof Array) { + for (const v of iv.value) { + // Binding + if (v.idShort === "href") { + if (form.href && form.href.length > 0) { + form.href = form.href + v.value; // TODO handle leading/trailing slashes + } else { + 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) { + form[v.idShort] = v.value; + // use valueType to convert the string value + if ( + v.valueType && + v.valueType && + v.valueType.dataObjectType && + v.valueType.dataObjectType.name && + typeof v.valueType.dataObjectType.name === "string" + ) { + // 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"; + break; + case "float": + case "double": + case "decimal": + case "integer": + case "nonPositiveInteger": + case "negativeInteger": + case "long": + case "int": + case "short": + case "byte": + case "nonNegativeInteger": + case "unsignedLong": + case "unsignedInt": + case "unsignedShort": + case "unsignedByte": + case "positiveInteger": + form[v.idShort] = Number(form[v.idShort]); + break; + // TODO handle more XSD types ? + } + } + } } } } @@ -367,14 +385,33 @@ export class AssetInterfaceDescriptionUtil { for (const vi of value) { // The first block of if condition is expected to be temporary. will be adjusted or removed when a decision on how the datapoint's datatype would be modelled is made for AID. - if (vi.interaction.constraints && vi.interaction.constraints instanceof Array) { - for (const constraint of vi.interaction.constraints) - if (constraint.type === "valueType") { - if (constraint.value === "float") { + if (vi.interaction.value && vi.interaction.value instanceof Array) { + for (const interactionValue of vi.interaction.value) + if (interactionValue.idShort === "type") { + if (interactionValue.value === "float") { thing.properties[key].type = "number"; } else { - thing.properties[key].type = constraint.value; + thing.properties[key].type = interactionValue.value; + } + } else if (interactionValue.idShort === "range") { + if (interactionValue.min) { + thing.properties[key].min = interactionValue.min; + } + if (interactionValue.max) { + thing.properties[key].max = interactionValue.max; } + } else if (interactionValue.idShort === "observable") { + thing.properties[key].observable = interactionValue.value === "true"; + } else if (interactionValue.idShort === "readOnly") { + thing.properties[key].readOnly = interactionValue.value === "true"; + } else if (interactionValue.idShort === "writeOnly") { + thing.properties[key].writeOnly = interactionValue.value === "true"; + } else if (interactionValue.idShort === "forms") { + // will be handled below + } else { + // handle other terms specifically? + const key2 = interactionValue.idShort; + thing.properties[key][key2] = interactionValue.value; } } diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 5be5b73f8..c71a882f5 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -23,7 +23,7 @@ import { promises as fs } from "fs"; class AssetInterfaceDescriptionUtilTest { private assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); - @test async "should correctly transform sample JSON AID_submodel HTTP v03 into a TD"() { + @test.skip async "should correctly transform sample JSON AID_submodel HTTP v03 into a TD"() { const modelFullString = await (await fs.readFile("test/util/AID_v03.json")).toString(); const modelFull = JSON.parse(modelFullString); const modelSub = modelFull.submodels[1]; @@ -51,7 +51,7 @@ class AssetInterfaceDescriptionUtilTest { .to.have.lengthOf(1); } - @test async "should correctly transform sample JSON AID_v03 into a TD"() { + @test.skip async "should correctly transform sample JSON AID_v03 into a TD"() { const modelAID = (await fs.readFile("test/util/AID_v03.json")).toString(); const td = this.assetInterfaceDescriptionUtil.transformAAS2TD( modelAID, @@ -170,7 +170,7 @@ class AssetInterfaceDescriptionUtilTest { .to.have.lengthOf(2); } - @test async "should correctly transform sample JSON AID_v03 for counter into a TD"() { + @test.skip async "should correctly transform sample JSON AID_v03 for counter into a TD"() { const modelAID = (await fs.readFile("test/util/AID_v03_counter.json")).toString(); const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); @@ -229,6 +229,139 @@ class AssetInterfaceDescriptionUtilTest { expect(td4Obj).to.not.have.property("properties"); } + @test async "should correctly transform counterHTTP_minimal into a TD"() { + const modelAID = (await fs.readFile("test/util/counterHTTP_minimal.json")).toString(); + const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); + + const tdObj = JSON.parse(td); + expect(tdObj).to.have.property("@context").that.equals("https://www.w3.org/2022/wot/td/v1.1"); + expect(tdObj).to.have.property("title").that.equals("counter"); + + expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(1); + expect(tdObj.securityDefinitions[tdObj.security[0]]).to.have.property("scheme").that.equals("nosec"); + + // check count property + expect(tdObj).to.have.property("properties").to.have.property("count"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("count") + .to.have.property("type") + .that.equals("integer"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("count") + .to.have.property("title") + .that.equals("Count"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("count") + .to.have.property("observable") + .that.equals(true); + expect(tdObj) + .to.have.property("properties") + .to.have.property("count") + .to.have.property("forms") + .to.be.an("array") + .to.have.lengthOf(1); + expect(tdObj.properties.count.forms[0]) + .to.have.property("href") + .to.eql("http://plugfest.thingweb.io:8083/counter" + "/properties/count"); + expect(tdObj.properties.count.forms[0]).to.have.property("htv:methodName").to.eql("GET"); + expect(tdObj.properties.count.forms[0]).to.have.property("contentType").to.eql("application/json"); + expect(tdObj.properties.count.forms[0]).not.to.have.property("security"); + + // check countAsImage property + expect(tdObj).to.have.property("properties").to.have.property("countAsImage"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("countAsImage") + .to.have.property("observable") + .that.equals(false); + expect(tdObj) + .to.have.property("properties") + .to.have.property("countAsImage") + .to.have.property("forms") + .to.be.an("array") + .to.have.lengthOf(1); + expect(tdObj.properties.countAsImage.forms[0]) + .to.have.property("href") + .to.eql("http://plugfest.thingweb.io:8083/counter" + "/properties/countAsImage"); + expect(tdObj.properties.countAsImage.forms[0]).to.have.property("htv:methodName").to.eql("GET"); + expect(tdObj.properties.countAsImage.forms[0]).to.have.property("contentType").to.eql("image/svg+xml"); + expect(tdObj.properties.countAsImage.forms[0]).not.to.have.property("security"); + + // check redDotImage property + expect(tdObj).to.have.property("properties").to.have.property("redDotImage"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("redDotImage") + .to.have.property("observable") + .that.equals(false); + expect(tdObj) + .to.have.property("properties") + .to.have.property("redDotImage") + .to.have.property("forms") + .to.be.an("array") + .to.have.lengthOf(1); + expect(tdObj.properties.redDotImage.forms[0]) + .to.have.property("href") + .to.eql("http://plugfest.thingweb.io:8083/counter" + "/properties/redDotImage"); + expect(tdObj.properties.redDotImage.forms[0]).to.have.property("htv:methodName").to.eql("GET"); + expect(tdObj.properties.redDotImage.forms[0]).to.have.property("contentType").to.eql("image/png;base64"); + expect(tdObj.properties.redDotImage.forms[0]).not.to.have.property("security"); + + // check count property + expect(tdObj).to.have.property("properties").to.have.property("lastChange"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("lastChange") + .to.have.property("type") + .that.equals("string"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("lastChange") + .to.have.property("title") + .that.equals("Last change"); + expect(tdObj) + .to.have.property("properties") + .to.have.property("lastChange") + .to.have.property("observable") + .that.equals(true); + expect(tdObj) + .to.have.property("properties") + .to.have.property("lastChange") + .to.have.property("forms") + .to.be.an("array") + .to.have.lengthOf(1); + expect(tdObj.properties.lastChange.forms[0]) + .to.have.property("href") + .to.eql("http://plugfest.thingweb.io:8083/counter" + "/properties/lastChange"); + expect(tdObj.properties.lastChange.forms[0]).to.have.property("htv:methodName").to.eql("GET"); + expect(tdObj.properties.lastChange.forms[0]).to.have.property("contentType").to.eql("application/json"); + expect(tdObj.properties.lastChange.forms[0]).not.to.have.property("security"); + + // TODO actions and events for counter thing (TBD by AAS) + + // check RegEx capability with fully qualified submodel + const td2 = this.assetInterfaceDescriptionUtil.transformAAS2TD( + modelAID, + `{"title": "counter"}`, + "InterfaceHTTP" + ); + const td2Obj = JSON.parse(td2); + expect(tdObj).to.deep.equal(td2Obj); + + // check RegEx capability with search pattern for submodel + const td3 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`, "HTTP*"); + const td3Obj = JSON.parse(td3); + expect(tdObj).to.deep.equal(td3Obj); + + // check RegEx capability with fully unknown submodel + const td4 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`, "OPC*"); + const td4Obj = JSON.parse(td4); + expect(td4Obj).to.not.have.property("properties"); + } + @test async "should correctly transform sample TD into JSON submodel"() { const td = `{"title": "testTD"}`; const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(td); diff --git a/packages/td-tools/test/util/counterHTTP_minimal.aasx b/packages/td-tools/test/util/counterHTTP_minimal.aasx new file mode 100644 index 0000000000000000000000000000000000000000..a87f633d203d2fdc1d4eaf720fe090751eeea7b6 GIT binary patch literal 5714 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 Date: Wed, 23 Aug 2023 14:41:10 +0200 Subject: [PATCH 05/30] refactor: initial setup for transforming TD to AAS/AID --- .../src/util/asset-interface-description.ts | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index cb62600af..f494dc0fa 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -514,10 +514,55 @@ export class AssetInterfaceDescriptionUtil { return this._transform(smInformation, template); } + /** + * Transform WoT ThingDescription (TD) to AAS in JSON format + * + * @param td input TD + * @returns transformed AAS in JSON format + */ + public transformTD2AAS(td: string): string { + // TODO selection like HTTP only or so.. + const submodel = this.transformTD2SM(td); + const submodelObj = JSON.parse(submodel); + const submodelId = submodelObj.id; + + // configuration + const aasName = "SampleAAS"; + const aasId = "https://example.com/ids/aas/7474_9002_6022_1115"; + + const aas = { + assetAdministrationShells: [ + { + idShort: aasName, + id: aasId, + assetInformation: { + // information needed? + }, + submodels: [ + { + type: "ModelReference", + keys: [ + { + type: "Submodel", + value: submodelId, + }, + ], + }, + ], + modelType: "AssetAdministrationShell", + }, + ], + submodels: [submodelObj], + conceptDescriptions: [], + }; + + return JSON.stringify(aas); + } + /** * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format * - * @param td input AID submodel in JSON format + * @param td input TD * @returns transformed AID submodel definition in JSON format */ public transformTD2SM(td: string): string { @@ -527,10 +572,21 @@ export class AssetInterfaceDescriptionUtil { // TODO // value entry "idShort": "EndpointMetadata" // value entry "idShort": "InterfaceMetadata" - const tdObject = { - idShort: thing.title, + const aidObject = { + idShort: "AssetInterfaceDescription", + id: "TODO XYZ", + kind: "Instance", + // semanticId needed? + description: [ + // TODO does this need to be an array or can it simply be a value + { + language: "en", + text: thing.title, // TODO should be description, where does title go to? later on in submodel? + }, + ], + submodels: [], }; - return JSON.stringify(tdObject); + return JSON.stringify(aidObject); } } From 8e7917ce6658bfb3f486ccacc7a919643420886a Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Wed, 23 Aug 2023 14:55:40 +0200 Subject: [PATCH 06/30] refactor: renaming --- packages/td-tools/src/util/README.md | 4 +- .../src/util/asset-interface-description.ts | 4 +- .../test/AssetInterfaceDescriptionTest.ts | 15 +- packages/td-tools/test/util/counterHTTP.aasx | Bin 0 -> 3039 bytes ...nterHTTP_minimal.json => counterHTTP.json} | 186 +----------------- .../test/util/counterHTTP_minimal.aasx | Bin 5714 -> 0 bytes 6 files changed, 23 insertions(+), 186 deletions(-) create mode 100644 packages/td-tools/test/util/counterHTTP.aasx rename packages/td-tools/test/util/{counterHTTP_minimal.json => counterHTTP.json} (77%) delete mode 100644 packages/td-tools/test/util/counterHTTP_minimal.aasx diff --git a/packages/td-tools/src/util/README.md b/packages/td-tools/src/util/README.md index 4d38befe2..3edf9053b 100644 --- a/packages/td-tools/src/util/README.md +++ b/packages/td-tools/src/util/README.md @@ -36,7 +36,7 @@ let assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); async function example() { try { - const aas = await fs.readFile("AID_v03_counter.json", { + const aas = await fs.readFile("counterHTTP.json", { encoding: "utf8", }); // transform AID to WoT TD @@ -69,4 +69,4 @@ example(); It will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count -Note: make sure that the file `AID_v03_counter.json` is in the same folder as the script. +Note: make sure that the file `counterHTTP.json` is in the same folder as the script. diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index f494dc0fa..36743a782 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -208,7 +208,7 @@ export class AssetInterfaceDescriptionUtil { submodel: Record, submodelRegex?: string ): void { - if (submodel instanceof Object && submodel.idShort && submodel.idShort === "AssetInterfaceDescription") { + if (submodel instanceof Object && submodel.idShort && submodel.idShort === "AssetInterfacesDescription") { if (submodel.submodelElements && submodel.submodelElements instanceof Array) { for (const submodelElement of submodel.submodelElements) { if (submodelElement instanceof Object) { @@ -573,7 +573,7 @@ export class AssetInterfaceDescriptionUtil { // value entry "idShort": "EndpointMetadata" // value entry "idShort": "InterfaceMetadata" const aidObject = { - idShort: "AssetInterfaceDescription", + idShort: "AssetInterfacesDescription", id: "TODO XYZ", kind: "Instance", // semanticId needed? diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index c71a882f5..65130c590 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -230,7 +230,7 @@ class AssetInterfaceDescriptionUtilTest { } @test async "should correctly transform counterHTTP_minimal into a TD"() { - const modelAID = (await fs.readFile("test/util/counterHTTP_minimal.json")).toString(); + const modelAID = (await fs.readFile("test/util/counterHTTP.json")).toString(); const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); const tdObj = JSON.parse(td); @@ -367,8 +367,19 @@ class AssetInterfaceDescriptionUtilTest { const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(td); const smObj = JSON.parse(sm); - expect(smObj).to.have.property("idShort").that.equals("testTD"); + expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); // TODO EndpointMetadata and InterfaceMetadata } + + @test async "should correctly transform sample TD into JSON AAS"() { + const td = `{"title": "testTD"}`; + const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(td); + + const aasObj = JSON.parse(sm); + expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); + expect(aasObj).to.have.property("submodels").to.be.an("array"); + + // TODO more checks + } } diff --git a/packages/td-tools/test/util/counterHTTP.aasx b/packages/td-tools/test/util/counterHTTP.aasx new file mode 100644 index 0000000000000000000000000000000000000000..f23b3de91d5449b3646ddcb623453fbbea573d57 GIT binary patch literal 3039 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 literal 0 HcmV?d00001 diff --git a/packages/td-tools/test/util/counterHTTP_minimal.json b/packages/td-tools/test/util/counterHTTP.json similarity index 77% rename from packages/td-tools/test/util/counterHTTP_minimal.json rename to packages/td-tools/test/util/counterHTTP.json index 9595d2295..1d4bfdbf3 100644 --- a/packages/td-tools/test/util/counterHTTP_minimal.json +++ b/packages/td-tools/test/util/counterHTTP.json @@ -25,181 +25,7 @@ ], "submodels": [ { - "idShort": "AssetInterfaceDescription", - "id": "https://example.com/ids/sm/8574_9002_6022_5297", - "kind": "Instance", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "submodelElements": [ - { - "idShort": "EndpointMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "base", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "modbus+tcp://192.168.1.187:502", - "modelType": "Property" - }, - { - "idShort": "contentType", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "application/octet-stream;byteSeq=BIG_ENDIAN", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "InterfaceMetadata", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "Properties", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "voltage", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "qualifiers": [ - { - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "type": "modbus:function", - "valueType": "xs:string", - "value": "readHoldingRegisters", - "valueId": { - "type": "ExternalReference", - "keys": [] - } - }, - { - "type": "modbus:address", - "valueType": "xs:string", - "value": "40001" - }, - { - "type": "modbus:quantity", - "valueType": "xs:string", - "value": "2" - } - ], - "embeddedDataSpecifications": [], - "valueType": "xs:double", - "value": "", - "modelType": "Property" - }, - { - "idShort": "voltage", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "qualifiers": [ - { - "type": "valueType", - "valueType": "xs:string", - "value": "double" - } - ], - "embeddedDataSpecifications": [], - "value": [ - { - "idShort": "modbus:function", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "readHoldingRegisters", - "modelType": "Property" - }, - { - "idShort": "modbus:address", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "40001", - "modelType": "Property" - }, - { - "idShort": "modbus:quantity", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "valueType": "xs:string", - "value": "2", - "modelType": "Property" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Operations", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - }, - { - "idShort": "Events", - "semanticId": { - "type": "ExternalReference", - "keys": [] - }, - "embeddedDataSpecifications": [], - "value": [], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "SubmodelElementCollection" - } - ], - "modelType": "Submodel" - }, - { - "idShort": "AssetInterfaceDescription", + "idShort": "AssetInterfacesDescription", "description": [ { "language": "en", @@ -256,7 +82,7 @@ "keys": [] }, "embeddedDataSpecifications": [], - "valueType": "xs:string", + "valueType": "xs:anyURI", "value": "http://plugfest.thingweb.io:8083", "modelType": "Property" }, @@ -391,7 +217,7 @@ "keys": [] }, "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", + "valueType": "xs:string", "value": "/counter/properties/count", "modelType": "Property" } @@ -456,7 +282,7 @@ "keys": [] }, "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", + "valueType": "xs:string", "value": "/counter/properties/countAsImage", "modelType": "Property" }, @@ -532,7 +358,7 @@ "keys": [] }, "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", + "valueType": "xs:string", "value": "/counter/properties/redDotImage", "modelType": "Property" }, @@ -620,7 +446,7 @@ "keys": [] }, "embeddedDataSpecifications": [], - "valueType": "xs:anyURI", + "valueType": "xs:string", "value": "/counter/properties/lastChange", "modelType": "Property" } diff --git a/packages/td-tools/test/util/counterHTTP_minimal.aasx b/packages/td-tools/test/util/counterHTTP_minimal.aasx deleted file mode 100644 index a87f633d203d2fdc1d4eaf720fe090751eeea7b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5714 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 Date: Wed, 23 Aug 2023 17:11:48 +0200 Subject: [PATCH 07/30] refactor: add transformation code to TODO: properties etc --- .../src/util/asset-interface-description.ts | 111 +++++++++++++++++- .../test/AssetInterfaceDescriptionTest.ts | 93 ++++++++++++++- 2 files changed, 194 insertions(+), 10 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 36743a782..f9d62dc2c 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -19,6 +19,7 @@ import { SecurityScheme } from "wot-thing-description-types"; import * as TDParser from "../td-parser"; import debug from "debug"; +import { ThingDescription } from "wot-typescript-definitions"; const namespace = "node-wot:td-tools:asset-interface-description-util"; const logDebug = debug(`${namespace}:debug`); const logInfo = debug(`${namespace}:info`); @@ -53,6 +54,17 @@ interface SubmodelInformation { endpointMetadataArray: Array>; } +// TODO can/shall we define als AAS/AID contructs? Difficult since idShort and value array is used *everywhere* +interface EndpointMetadata { + idShort: "EndpointMetadata"; + value: Array; + modelType: "SubmodelElementCollection"; +} + +interface EndpointMetadataValue { + idShort: string; // base, contentType, securityDefinitions +} + const noSecSS: SecurityScheme = { scheme: "nosec" }; const noSecName = 0 + "_sc"; @@ -565,9 +577,13 @@ export class AssetInterfaceDescriptionUtil { * @param td input TD * @returns transformed AID submodel definition in JSON format */ - public transformTD2SM(td: string): string { - const thing = TDParser.parseTD(td); - console.log("TD " + thing.title + " parsed..."); + public transformTD2SM(tdAsString: string): string { + const td: ThingDescription = TDParser.parseTD(tdAsString); + + console.log("TD " + td.title + " parsed..."); + + // configuration + const submodelElementIdShort = "InterfaceHTTP"; // TODO // value entry "idShort": "EndpointMetadata" @@ -581,12 +597,97 @@ export class AssetInterfaceDescriptionUtil { // TODO does this need to be an array or can it simply be a value { language: "en", - text: thing.title, // TODO should be description, where does title go to? later on in submodel? + text: td.title, // TODO should be description, where does title go to? later on in submodel? }, ], - submodels: [], + submodelElements: [ + { + idShort: submodelElementIdShort, + // semanticId needed? + // embeddedDataSpecifications needed? + value: [ + // support + this.createEndpointMetadata(td), // EndpointMetadata + this.createInterfaceMetadata(td), // InterfaceMetadata + // externalDescriptor ? + ], + modelType: "SubmodelElementCollection", + }, + ], + modelType: "Submodel", }; return JSON.stringify(aidObject); } + + private createEndpointMetadata(td: ThingDescription): Record { + const values: Array = []; + + // base ? + if (td.base) { + values.push({ + idShort: "base", + valueType: "xs:anyURI", + value: td.base, // TODO + modelType: "Property", + }); + } + + // TODO wrong place.. not allowed in TD spec? + /* + { + idShort: "contentType", + valueType: "xs:string", + value: "application/json", // TODO + modelType: "Property", + }, + */ + + // securityDefinitions + const secValues: Array = []; + for (const secKey in td.securityDefinitions) { + const secValue: SecurityScheme = td.securityDefinitions[secKey]; + secValues.push({ + idShort: secValue.scheme, + modelType: "SubmodelElementCollection", // TODO correct or Property ? + // modelType: "Property" + }); + } + + values.push({ + idShort: "securityDefinitions", + value: [ + { + idShort: "scheme", + value: secValues, + modelType: "SubmodelElementCollection", + }, + ], + modelType: "SubmodelElementCollection", + }); + + const endpointMetadata: Record = { + idShort: "EndpointMetadata", + // semanticId ? + // embeddedDataSpecifications ? + value: values, + modelType: "SubmodelElementCollection", + }; + + return endpointMetadata; + } + + private createInterfaceMetadata(td: ThingDescription): Record { + const interfaceMetadata: Record = { + idShort: "InterfaceMetadata", + // semanticId ? + // embeddedDataSpecifications ? + value: [ + // TODO + ], + modelType: "SubmodelElementCollection", + }; + + return interfaceMetadata; + } } diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 65130c590..d1b38c1a6 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -18,6 +18,7 @@ import { expect } from "chai"; import { AssetInterfaceDescriptionUtil } from "../src/util/asset-interface-description"; import { promises as fs } from "fs"; +import { ThingDescription } from "wot-typescript-definitions"; @suite("tests to verify the Asset Interface Description Utils") class AssetInterfaceDescriptionUtilTest { @@ -363,13 +364,95 @@ class AssetInterfaceDescriptionUtilTest { } @test async "should correctly transform sample TD into JSON submodel"() { - const td = `{"title": "testTD"}`; - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(td); + const base = "https://www.example.com/"; + const td: ThingDescription = { + "@context": "https://www.w3.org/2022/wot/td/v1.1", + title: "testTD", + securityDefinitions: { + basic_sc: { + scheme: "basic", + in: "header", + }, + }, + security: "basic_sc", + base: base, + properties: { + status: { + type: "string", + observable: true, + forms: [ + { + href: "stat", + }, + ], + }, + }, + }; + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(td)); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); - - // TODO EndpointMetadata and InterfaceMetadata + 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 hasEndpointMetadata = false; + for (const smValue of smInterface.value) { + 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 hasBase = false; + let hasContentType = false; + let hasSecurityDefinitions = false; + for (const endpointMetadataValue of endpointMetadata.value) { + if (endpointMetadataValue.idShort === "base") { + hasBase = true; + expect(endpointMetadataValue.value).to.equal(base); + } else if (endpointMetadataValue.idShort === "contentType") { + hasContentType = true; + } else if (endpointMetadataValue.idShort === "securityDefinitions") { + hasSecurityDefinitions = true; + expect(endpointMetadataValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasScheme = false; + for (const securityDefinitionValue of endpointMetadataValue.value) { + if (securityDefinitionValue.idShort === "scheme") { + hasScheme = 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 === "basic") { + hasBasic = true; + } + } + expect(hasBasic).to.equal(true); + } + } + expect(hasScheme).to.equal(true); + + // expect(endpointMetadataValue.value).to.equal("basic"); + } + } + expect(hasBase).to.equal(true); + expect(hasContentType).to.equal(false); + expect(hasBase).to.equal(hasSecurityDefinitions); + } + } + expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); + + // TODO InterfaceMetadata with properties etc + let hasInterfaceMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "InterfaceMetadata") { + hasInterfaceMetadata = true; + } + } + expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); } @test async "should correctly transform sample TD into JSON AAS"() { @@ -378,7 +461,7 @@ class AssetInterfaceDescriptionUtilTest { const aasObj = JSON.parse(sm); expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); - expect(aasObj).to.have.property("submodels").to.be.an("array"); + expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1); // TODO more checks } From 86d8c9a68c4ed471ffd4f8a2306ead99e33ee627 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 24 Aug 2023 12:41:05 +0200 Subject: [PATCH 08/30] refactor: simple interaction checks for TD to AID --- .../src/util/asset-interface-description.ts | 100 +++++++++++++++++- .../test/AssetInterfaceDescriptionTest.ts | 63 ++++++++++- 2 files changed, 157 insertions(+), 6 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index f9d62dc2c..1b559da76 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -20,6 +20,7 @@ import * as TDParser from "../td-parser"; import debug from "debug"; import { ThingDescription } from "wot-typescript-definitions"; +import { PropertyElement } from "wot-thing-model-types"; const namespace = "node-wot:td-tools:asset-interface-description-util"; const logDebug = debug(`${namespace}:debug`); const logInfo = debug(`${namespace}:info`); @@ -678,13 +679,106 @@ export class AssetInterfaceDescriptionUtil { } private createInterfaceMetadata(td: ThingDescription): Record { + const properties: Array = []; + if (td.properties) { + for (const propertyKey in td.properties) { + const propertyValue: PropertyElement = td.properties[propertyKey]; + + const propertyValues: Array = []; + // type + if (propertyValue.type) { + propertyValues.push({ + idShort: "type", + valueType: "xs:string", + value: propertyValue.type, + modelType: "Property", + }); + } + // title + if (propertyValue.title) { + propertyValues.push({ + idShort: "title", + valueType: "xs:string", + value: propertyValue.title, + modelType: "Property", + }); + } + // observable + if (propertyValue.observable) { + propertyValues.push({ + idShort: "observable", + valueType: "xs:boolean", + value: `${propertyValue.observable}`, // in AID represented as string + modelType: "Property", + }); + } + // readOnly and writeOnly marked as EXTERNAL in AID spec + // range and others? Simply add them as is? + + // forms + if (propertyValue.forms) { + const propertyForm: Array = []; + /* for (const FormElementProperty in propertyValue.forms) { + // TODO AID for now supports just *one* href/form + } */ + if (propertyValue.forms.length > 0) { + // pick first one for now (TODO change in future) + const propertyForm0 = propertyValue.forms[0]; + // "idShort": "htv:methodName", + if (propertyForm0.href) { + propertyForm.push({ + idShort: "href", + value: propertyForm0.href, + modelType: "Property", + }); + } + // TODO other terms? + } + + propertyValues.push({ + idShort: "forms", + value: propertyForm, + modelType: "SubmodelElementCollection", + }); + } + + properties.push({ + idShort: propertyKey, + // TODO description + value: propertyValues, + modelType: "SubmodelElementCollection", + }); + } + } + + const values: Array = []; + + // Properties + values.push({ + idShort: "Properties", + value: properties, + modelType: "SubmodelElementCollection", + }); + + // Actions - TBD by AID + values.push({ + idShort: "Actions", + value: [], + modelType: "SubmodelElementCollection", + }); + + // Events - TBD by AID + values.push({ + idShort: "Events", + value: [], + modelType: "SubmodelElementCollection", + }); + const interfaceMetadata: Record = { idShort: "InterfaceMetadata", // semanticId ? // embeddedDataSpecifications ? - value: [ - // TODO - ], + value: values, modelType: "SubmodelElementCollection", }; diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index d1b38c1a6..1a39215a5 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -434,8 +434,6 @@ class AssetInterfaceDescriptionUtilTest { } } expect(hasScheme).to.equal(true); - - // expect(endpointMetadataValue.value).to.equal("basic"); } } expect(hasBase).to.equal(true); @@ -450,6 +448,65 @@ class AssetInterfaceDescriptionUtilTest { 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 hasPropertyStatus = false; + for (const propertyValue of interactionValues.value) { + if (propertyValue.idShort === "status") { + hasPropertyStatus = true; + expect(propertyValue) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasType = false; + let hasTitle = false; + let hasObservable = 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; + } else if (propProperty.idShort === "observable") { + hasObservable = true; + expect(propProperty.value).to.equal("true"); + } 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; + for (const formEntry of propProperty.value) { + if (formEntry.idShort === "href") { + hasHref = true; + expect(formEntry.value).to.be.oneOf([ + "stat", + "https://www.example.com/stat", + ]); + } + } + expect(hasHref).to.equal(true); + } + } + expect(hasType).to.equal(true); + expect(hasTitle).to.equal(false); + expect(hasObservable).to.equal(true); + expect(hasForms).to.equal(true); + } + } + expect(hasPropertyStatus).to.equal(true); + } + } + expect(hasProperties).to.equal(true); } } expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); @@ -463,6 +520,6 @@ class AssetInterfaceDescriptionUtilTest { expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1); - // TODO more checks + // Note: proper AID submodel checks done in previous test-case } } From 5c0b5ee7ca6946a5dfea10437aa5dd637822dd9c Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 24 Aug 2023 12:53:19 +0200 Subject: [PATCH 09/30] docs: remove TODO it does some basic checking --- packages/td-tools/test/AssetInterfaceDescriptionTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 1a39215a5..1e3942f89 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -443,7 +443,7 @@ class AssetInterfaceDescriptionUtilTest { } expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); - // TODO InterfaceMetadata with properties etc + // InterfaceMetadata with properties etc let hasInterfaceMetadata = false; for (const smValue of smInterface.value) { if (smValue.idShort === "InterfaceMetadata") { From be184245773478d657621ed0e210687d18279a8d Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 24 Aug 2023 14:36:06 +0200 Subject: [PATCH 10/30] refactor: handle new security/securityDefinititions by @Kaz040 --- .../src/util/asset-interface-description.ts | 170 ++++++++++++------ .../test/AssetInterfaceDescriptionTest.ts | 24 ++- packages/td-tools/test/util/counterHTTP.aasx | Bin 3039 -> 3099 bytes packages/td-tools/test/util/counterHTTP.json | 31 +++- 4 files changed, 157 insertions(+), 68 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 1b559da76..e3ebe0bcd 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -94,25 +94,69 @@ export class AssetInterfaceDescriptionUtil { return ""; // TODO what is the right value if information cannot be found } - private getSecuritySchemesFromEndpointMetadata( - endpointMetadata?: Record - ): Array | undefined { + private getSecurityDefinitionsFromEndpointMetadata(endpointMetadata?: Record): { + [k: string]: SecurityScheme; + } { + const securityDefinitions: { + [k: string]: SecurityScheme; + } = {}; + if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { for (const v of endpointMetadata.value) { if (v.idShort === "securityDefinitions") { - const securitySchemes: Array = []; + // const securitySchemes: Array = []; if (v.value && v.value instanceof Array) { - for (const securityDefinitionsValue of v.value) { - if (securityDefinitionsValue.idShort === "scheme") { + for (const securityDefinitionsValues of v.value) { + if (securityDefinitionsValues.idShort) { + // key + if (securityDefinitionsValues.value instanceof Array) { + for (const securityDefinitionsValue of securityDefinitionsValues.value) { + if (securityDefinitionsValue.idShort === "scheme") { + if (securityDefinitionsValue.value) { + const ss: SecurityScheme = { scheme: securityDefinitionsValue.value }; + securityDefinitions[securityDefinitionsValues.idShort] = ss; + } + /* if (securityDefinitionsValue.value && securityDefinitionsValue.value instanceof Array) { + for (const secValue of securityDefinitionsValue.value) { + // allow all *other* security schemes like "uasec" as well + const ss: SecurityScheme = { scheme: secValue.idShort }; + securitySchemes.push(ss); + / * if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { + const ss : SecurityScheme = { scheme: secValue.idShort}; + securitySchemes.push(ss); + } * / + if (secValue.value && secValue.value instanceof Array) { + for (const v of secValue.value) { + if ( + v.idShort && + typeof v.idShort === "string" && + v.idShort.length > 0 && + v.value + ) { + ss[v.idShort] = v.value; + } + } + } + } + } */ + } + } + } + /* securityDefinitions[securityDefinitionsValue.idShort] = { + + } */ + } + + /* if (securityDefinitionsValue.idShort === "scheme") { if (securityDefinitionsValue.value && securityDefinitionsValue.value instanceof Array) { for (const secValue of securityDefinitionsValue.value) { // allow all *other* security schemes like "uasec" as well const ss: SecurityScheme = { scheme: secValue.idShort }; securitySchemes.push(ss); - /* if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { + / * if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { const ss : SecurityScheme = { scheme: secValue.idShort}; securitySchemes.push(ss); - } */ + } * / if (secValue.value && secValue.value instanceof Array) { for (const v of secValue.value) { if ( @@ -127,14 +171,34 @@ export class AssetInterfaceDescriptionUtil { } } } + } */ + } + } + } + } + } + return securityDefinitions; + } + + private getSecurityFromEndpointMetadata( + endpointMetadata?: Record + ): string | [string, ...string[]] { + const security: string[] = []; + if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { + for (const v of endpointMetadata.value) { + if (v.idShort === "security") { + if (v.value && v.value instanceof Array) { + for (const securityValue of v.value) { + if (securityValue.value) { + security.push(securityValue.value); } } } - return securitySchemes; } } } - return undefined; + + return security as string | [string, ...string[]]; } private createInteractionForm(vi: AASInteraction, addSecurity: boolean): TD.Form { @@ -144,7 +208,7 @@ export class AssetInterfaceDescriptionUtil { }; // need to add security at form level at all ? if (addSecurity) { - const securitySchemes = this.getSecuritySchemesFromEndpointMetadata(vi.endpointMetadata); + const securitySchemes = this.getSecurityDefinitionsFromEndpointMetadata(vi.endpointMetadata); if (securitySchemes === undefined) { form.security = [noSecName]; } else { @@ -348,39 +412,24 @@ export class AssetInterfaceDescriptionUtil { // Security in AID is defined for each submodel // add "securityDefinitions" globally and add them on form level if necessary - // Note: possible collisions for "security" names handled by cnt + // TODO: possible collisions for "security" names *could* be handled by cnt if (!thing.securityDefinitions) { thing.securityDefinitions = {}; } - let cnt = 1; - const secSchemeNamesAll = new Array(); + // let cnt = 1; const secNamesForEndpointMetadata = new Map, string[]>(); for (const endpointMetadata of smInformation.endpointMetadataArray) { const secNames: Array = []; - const securitySchemes = this.getSecuritySchemesFromEndpointMetadata(endpointMetadata); - if (securitySchemes === undefined) { - // we need "nosec" scheme - thing.securityDefinitions[noSecName] = noSecSS; - secSchemeNamesAll.push(noSecName); - secNames.push(noSecName); - } else { - // iterate over securitySchemes - for (const secScheme of securitySchemes) { - const secName = cnt + "_sc"; - thing.securityDefinitions[secName] = secScheme; - secSchemeNamesAll.push(secName); - secNames.push(secName); - cnt++; - } + thing.securityDefinitions = this.getSecurityDefinitionsFromEndpointMetadata(endpointMetadata); + thing.security = this.getSecurityFromEndpointMetadata(endpointMetadata); + // iterate over securitySchemes + for (const [key, value] of Object.entries(thing.securityDefinitions)) { + // console.log(key, value); + // TODO we could change the name to avoid name collisions. Shall we do so? + secNames.push(key); } secNamesForEndpointMetadata.set(endpointMetadata, secNames); } - if (secSchemeNamesAll.length === 0) { - thing.securityDefinitions.nosec_sc = noSecSS; - thing.security = [noSecName]; - } else { - thing.security = secSchemeNamesAll as [string, ...string[]]; - } // add interactions // 1. properties @@ -584,11 +633,9 @@ export class AssetInterfaceDescriptionUtil { console.log("TD " + td.title + " parsed..."); // configuration + // TODO pass as argument protocol binding prefix like "http" and use this as name and for forms const submodelElementIdShort = "InterfaceHTTP"; - // TODO - // value entry "idShort": "EndpointMetadata" - // value entry "idShort": "InterfaceMetadata" const aidObject = { idShort: "AssetInterfacesDescription", id: "TODO XYZ", @@ -608,8 +655,8 @@ export class AssetInterfaceDescriptionUtil { // embeddedDataSpecifications needed? value: [ // support - this.createEndpointMetadata(td), // EndpointMetadata - this.createInterfaceMetadata(td), // InterfaceMetadata + this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions + this.createInterfaceMetadata(td), // InterfaceMetadata like proprties, actions and events // externalDescriptor ? ], modelType: "SubmodelElementCollection", @@ -644,26 +691,43 @@ export class AssetInterfaceDescriptionUtil { }, */ + // security + const securityValues: Array = []; + if (td.security) { + for (const secKey of td.security) { + securityValues.push({ + valueType: "xs:string", + value: secKey, + modelType: "Property", + }); + } + } + values.push({ + idShort: "security", + value: securityValues, + modelType: "SubmodelElementCollection", + }); + // securityDefinitions - const secValues: Array = []; + const securityDefinitionsValues: Array = []; for (const secKey in td.securityDefinitions) { const secValue: SecurityScheme = td.securityDefinitions[secKey]; - secValues.push({ - idShort: secValue.scheme, - modelType: "SubmodelElementCollection", // TODO correct or Property ? - // modelType: "Property" + securityDefinitionsValues.push({ + idShort: secKey, + value: [ + { + idShort: "scheme", + valueType: "xs:string", + value: secValue.scheme, + modelType: "Property", + }, + ], + modelType: "SubmodelElementCollection", }); } - values.push({ idShort: "securityDefinitions", - value: [ - { - idShort: "scheme", - value: secValues, - modelType: "SubmodelElementCollection", - }, - ], + value: securityDefinitionsValues, modelType: "SubmodelElementCollection", }); diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 1e3942f89..b43a7f58d 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -238,6 +238,8 @@ class AssetInterfaceDescriptionUtilTest { expect(tdObj).to.have.property("@context").that.equals("https://www.w3.org/2022/wot/td/v1.1"); expect(tdObj).to.have.property("title").that.equals("counter"); + expect(tdObj).to.have.property("securityDefinitions").to.be.an("object"); + expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(1); expect(tdObj.securityDefinitions[tdObj.security[0]]).to.have.property("scheme").that.equals("nosec"); @@ -403,6 +405,7 @@ class AssetInterfaceDescriptionUtilTest { expect(endpointMetadata).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); let hasBase = false; let hasContentType = false; + let hasSecurity = false; let hasSecurityDefinitions = false; for (const endpointMetadataValue of endpointMetadata.value) { if (endpointMetadataValue.idShort === "base") { @@ -410,35 +413,44 @@ class AssetInterfaceDescriptionUtilTest { expect(endpointMetadataValue.value).to.equal(base); } else if (endpointMetadataValue.idShort === "contentType") { hasContentType = true; + } else 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("basic_sc"); } else if (endpointMetadataValue.idShort === "securityDefinitions") { hasSecurityDefinitions = true; expect(endpointMetadataValue) .to.have.property("value") .to.be.an("array") .to.have.lengthOf.greaterThan(0); - let hasScheme = false; + let hasBasicSC = false; for (const securityDefinitionValue of endpointMetadataValue.value) { - if (securityDefinitionValue.idShort === "scheme") { - hasScheme = true; + if (securityDefinitionValue.idShort === "basic_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 === "basic") { + if (sec.idShort === "scheme") { hasBasic = true; + expect(sec.value).to.equal("basic"); } } expect(hasBasic).to.equal(true); } } - expect(hasScheme).to.equal(true); + expect(hasBasicSC).to.equal(true); } } expect(hasBase).to.equal(true); expect(hasContentType).to.equal(false); - expect(hasBase).to.equal(hasSecurityDefinitions); + expect(hasSecurity).to.equal(true); + expect(hasSecurityDefinitions).to.equal(true); } } expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); diff --git a/packages/td-tools/test/util/counterHTTP.aasx b/packages/td-tools/test/util/counterHTTP.aasx index f23b3de91d5449b3646ddcb623453fbbea573d57..3815efa46d53cea51740d62de78dd5dff9e0e496 100644 GIT binary patch delta 1867 zcmV-R2ekO#7n>N6QVM3C(uDX1004JWkz6Q$&2rl|5WZ(7@4(5mV^gx!*l|_OII$=7 z!*? z|NJ`u@0JG4bHNkF(sexk>$ABKK0h^0V!PBk;B#=CL&{7-xY;v1&xTK*7{-8b4&qFI zQcx}eA}I4ll9H?L0)nU?DX2Y~vp@_-#PuCGI~xtmxG6z1jM;&uqC2#hi*#J^bKF1f zk6&~;N8{(6qoZ-R+x=NivL3Ivxb&tha1{%yp1AJ~_3YvGoeu+Z{XIc6bJ&D9XKI=P zUSz-7>l}B-{qAvReA4M1jr%7rdIK|mJwak-?t?|A)QY;eVP=?knD$*Gp7KsyxZG(y0z`Ct%xP zpA$hwQIJ+1pP3>zCiwyLArLvyF&0oz;XuB;>uHrKE%_;i8|4P;MomC}jhtB*DK%_W zV3#j^AOBZnn0R*|uP!Sv9DBixiuq!qoB%9``Wy~~3`Cz{VtQy!2cMZR>xxy)6jO3u zc(%`|C*FY|HtN1WV69cwwM?CLhQby6Om!>Q?;_A7(j8|TvTQV&XhH6%uCZ4E^jR>QF= zmSuQW!n7H#rPx+;$+U%9b5uh)pxgLzf^Vh4VrQ=4&>=~2M5yVp)k8#49;nGMtIp6Q z%RQyfl>=5-!Iq0<>shDE4uY zNEugVJti}@c(G`IchYE=w(_Mphu{lfN-;x8qGhkoXxa})9_`{-zC4(W%9S!A#Dd^D zB3dRYDq26=tx)-kYL794_!FVR0183jR1YH0ka*l%mVqlUfPS%)JOhaWw5^igjRjMr zP2^0oTrMbXBMiF(Q~oQaBgZdHHZsyCy; zAyzC_kzvz+P7R_;TyIdj?R2$dyHz}e*%`ldu`u{Ssz`%yWAy2@K|`4yX-Ro@BNlid zC0$SI*xyrho(Lspmg$pus$>W+FKWo_%tM_+G#kF

uF~e^I*i)9JQtQWFK_em zwj}!|dHE{G{ND9i<+C-Z(QRbdNn}ttxyf&5KP_ECiU%M}og{0jV+4C*2y!tDZIJV{2lYKA0d~f!Pd;{pY<~7``ob6_r zDz+Sd!o{n3wNph8tpIzAyc?Ec-eA)o>i)CB8`-Ku(*&L(h)Q9q4^{GkMF?M-5~1k$4Fm`BN}B^t)eU9BK9tv` z$V0^X2g4w7R3_)9m1OVJobZ&ZpY8l3JG#oq49)tQz|_5doA(Gx`7Q88!h%BB2WH{~ zn$6+=vpxrk0}5uI(uDX1004JWlhO)a0eX`$3pfF?lWGe{0)~d6y9ehci?2(u_;+@!voH z8sOeipScdWLR&hv%YS{g5W?f9x=t*Ix(9p#wtYyMP6*crdhglj=@U&e2hO@J=p~sIBG^3auSSq?hlQ~Go6+g$rDc`-2b^ZZ-N0RPwD{S^qj${mHSk>ac$@H1x371{3-w7wSn$ay=`$p>OIa(r1yo7B`0jWOTlNdXV+@ z(A=&7e@0B;=fF*$dUC)V_qW!qNttZo&d0*1?)(~-Y0p@-2V@Q-z(H4E(w4`LqgO1D zjA)S!wCe>9iDr{+NfnmpJ$qA+DfiI@72yyjY^8T~ZS0D$EmE z2*V^h+{&{rY><;iD%LjIUNPGSbHjNPp|FaA->$DeWWB#wq9#)1NmeMGY5->nmIc;1 z5o8=CX%+FAA#!t)=Q9s{k&_)`0R!j=3?eJ@RDJmN*gZ2-b%I>?84=I3 zqtW$0xU9L_nWcVuNvBdU1D}e!bC_Wzsbt(6dlm80OqFSMnhWexuE9zMS~8Jh#ARsN zYCh^&bQ{aov2blmx3_p&@ufs-FxJ2U3Zu5oh-EpTK^aj{pwu0Ip-siIDxmkOWNDdu zKZNO=LYH#4NV%p&88tF){>nv$tOBj)(wDDnWV(cCKJd|_g{IiYH3sEe891NmTfWw? zJ8246M)IXO2mcFT`7lRGqE(a6=-LlQp2D@2e0e?*TQ0N^Ap(NSQ0SRxsA&0Rw?gHw z$z-T%O^j{l=c>7Xxz@aF{E4c<1PXrXP_1U}>T;Bw@_@AZ%pKa?q=O30@6E??i;vBA~4 z*RIUnOi(uE;NOyIJ87qk*+ATe;tF3*T)UBWOVlGbpDK!fsXazLRlL@6HANXKcp+|2 z9S{p`(jInK&S@$2kzPPTAyil~7#%<#SxS!dX{=QAGN5COA zETYKp(M}zrPG0}st0mj5*eRWz@khrd#VV+X&U>^uDO-1#r^1(N3lZ*gYj>^933*s=*1uNL)A z6+JWn_7-{9OvSv$Wxdvoi@yRMR(e;Am4PS4fhr|<)P{%7}zru zwVM|-8_z)ND|-~p{=7Dr+ju{!zPOcl%5<83$Zs7(w=cb%RYn)NMnscYdjuc4Fjo8XCr2Zd@k^u#TVqyGW3dIyRF w3Z@mqjl2c`0Jc$+4hvoZK9g+=I0AMElg Date: Thu, 24 Aug 2023 14:40:25 +0200 Subject: [PATCH 11/30] refactor: restructure test input --- .../test/AssetInterfaceDescriptionTest.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index b43a7f58d..4e8eea79b 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -365,32 +365,33 @@ class AssetInterfaceDescriptionUtilTest { expect(td4Obj).to.not.have.property("properties"); } - @test async "should correctly transform sample TD into JSON submodel"() { - const base = "https://www.example.com/"; - const td: ThingDescription = { - "@context": "https://www.w3.org/2022/wot/td/v1.1", - title: "testTD", - securityDefinitions: { - basic_sc: { - scheme: "basic", - in: "header", - }, + td1Base = "https://www.example.com/"; + td1: ThingDescription = { + "@context": "https://www.w3.org/2022/wot/td/v1.1", + title: "testTD", + securityDefinitions: { + basic_sc: { + scheme: "basic", + in: "header", }, - security: "basic_sc", - base: base, - properties: { - status: { - type: "string", - observable: true, - forms: [ - { - href: "stat", - }, - ], - }, + }, + security: "basic_sc", + base: this.td1Base, + properties: { + status: { + type: "string", + observable: true, + forms: [ + { + href: "stat", + }, + ], }, - }; - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(td)); + }, + }; + + @test async "should correctly transform sample TD into JSON submodel"() { + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1)); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); @@ -410,7 +411,7 @@ class AssetInterfaceDescriptionUtilTest { for (const endpointMetadataValue of endpointMetadata.value) { if (endpointMetadataValue.idShort === "base") { hasBase = true; - expect(endpointMetadataValue.value).to.equal(base); + expect(endpointMetadataValue.value).to.equal(this.td1Base); } else if (endpointMetadataValue.idShort === "contentType") { hasContentType = true; } else if (endpointMetadataValue.idShort === "security") { @@ -525,8 +526,7 @@ class AssetInterfaceDescriptionUtilTest { } @test async "should correctly transform sample TD into JSON AAS"() { - const td = `{"title": "testTD"}`; - const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(td); + const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(this.td1)); const aasObj = JSON.parse(sm); expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); From 4fded0c1c77bf6992a1c014f6170252d26d8b010 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 24 Aug 2023 15:00:58 +0200 Subject: [PATCH 12/30] fix: add terms needed by AASX Explorer --- packages/td-tools/src/util/asset-interface-description.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index e3ebe0bcd..5255ada4b 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -598,7 +598,7 @@ export class AssetInterfaceDescriptionUtil { idShort: aasName, id: aasId, assetInformation: { - // information needed? + assetKind: "Type", }, submodels: [ { @@ -792,6 +792,7 @@ export class AssetInterfaceDescriptionUtil { if (propertyForm0.href) { propertyForm.push({ idShort: "href", + valueType: "xs:string", value: propertyForm0.href, modelType: "Property", }); From de51585832dde657bfbfa728a76e07e9c066e8b3 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 28 Aug 2023 14:56:50 +0200 Subject: [PATCH 13/30] feat: add ability to choose protocol prefix of interest --- .../src/util/asset-interface-description.ts | 182 +++++++++++------- .../test/AssetInterfaceDescriptionTest.ts | 32 ++- 2 files changed, 138 insertions(+), 76 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 5255ada4b..fcdcb6ed7 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -20,7 +20,7 @@ import * as TDParser from "../td-parser"; import debug from "debug"; import { ThingDescription } from "wot-typescript-definitions"; -import { PropertyElement } from "wot-thing-model-types"; +import { FormElementBase, PropertyElement } from "wot-thing-model-types"; const namespace = "node-wot:td-tools:asset-interface-description-util"; const logDebug = debug(`${namespace}:debug`); const logInfo = debug(`${namespace}:info`); @@ -580,13 +580,24 @@ export class AssetInterfaceDescriptionUtil { * Transform WoT ThingDescription (TD) to AAS in JSON format * * @param td input TD + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) * @returns transformed AAS in JSON format */ - public transformTD2AAS(td: string): string { - // TODO selection like HTTP only or so.. - const submodel = this.transformTD2SM(td); - const submodelObj = JSON.parse(submodel); - const submodelId = submodelObj.id; + public transformTD2AAS(td: string, protocols: string[]): string { + const submodelObjs: unknown[] = []; + let submodelId; + + // apply selection like HTTP only or so.. + for (const protocol of protocols) { + const submodel = this.transformTD2SM(td, protocol); + const submodelObj = JSON.parse(submodel); + submodelObjs.push(submodelObj); + if (submodelId === undefined) { + submodelId = submodelObj.id; + } + } + + // TODO how should the structure w.r.t. submodel ID reference look like if there are several submodels // configuration const aasName = "SampleAAS"; @@ -614,7 +625,7 @@ export class AssetInterfaceDescriptionUtil { modelType: "AssetAdministrationShell", }, ], - submodels: [submodelObj], + submodels: submodelObjs, conceptDescriptions: [], }; @@ -625,16 +636,16 @@ export class AssetInterfaceDescriptionUtil { * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format * * @param td input TD + * @param protocol protocol prefix of interest (e.g., "http") * @returns transformed AID submodel definition in JSON format */ - public transformTD2SM(tdAsString: string): string { + public transformTD2SM(tdAsString: string, protocol: string): string { const td: ThingDescription = TDParser.parseTD(tdAsString); console.log("TD " + td.title + " parsed..."); - // configuration - // TODO pass as argument protocol binding prefix like "http" and use this as name and for forms - const submodelElementIdShort = "InterfaceHTTP"; + // use protocol binding prefix like "http" for name + const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); const aidObject = { idShort: "AssetInterfacesDescription", @@ -656,7 +667,7 @@ export class AssetInterfaceDescriptionUtil { value: [ // support this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions - this.createInterfaceMetadata(td), // InterfaceMetadata like proprties, actions and events + this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events // externalDescriptor ? ], modelType: "SubmodelElementCollection", @@ -742,100 +753,125 @@ export class AssetInterfaceDescriptionUtil { return endpointMetadata; } - private createInterfaceMetadata(td: ThingDescription): Record { + private createInterfaceMetadata(td: ThingDescription, protocol: string): Record { const properties: Array = []; - if (td.properties) { - for (const propertyKey in td.properties) { - const propertyValue: PropertyElement = td.properties[propertyKey]; - - const propertyValues: Array = []; - // type - if (propertyValue.type) { - propertyValues.push({ - idShort: "type", - valueType: "xs:string", - value: propertyValue.type, - modelType: "Property", - }); - } - // title - if (propertyValue.title) { - propertyValues.push({ - idShort: "title", - valueType: "xs:string", - value: propertyValue.title, - modelType: "Property", - }); - } - // observable - if (propertyValue.observable) { - propertyValues.push({ - idShort: "observable", - valueType: "xs:boolean", - value: `${propertyValue.observable}`, // in AID represented as string - modelType: "Property", - }); - } - // readOnly and writeOnly marked as EXTERNAL in AID spec - // range and others? Simply add them as is? + const actions: Array = []; + const events: Array = []; + + if (protocol) { + // Properties + if (td.properties) { + for (const propertyKey in td.properties) { + const propertyValue: PropertyElement = td.properties[propertyKey]; + + // check whether protocol prefix exists for a form + let formElementPicked: FormElementBase | undefined; + if (propertyValue.forms) { + for (const formElementProperty of propertyValue.forms) { + if (formElementProperty.href?.startsWith(protocol)) { + formElementPicked = formElementProperty; + // found matching form --> abort loop + break; + } + } + } + if (formElementPicked === undefined) { + // do not add this property, since there will be no href of interest + continue; + } + + const propertyValues: Array = []; + // type + if (propertyValue.type) { + propertyValues.push({ + idShort: "type", + valueType: "xs:string", + value: propertyValue.type, + modelType: "Property", + }); + } + // title + if (propertyValue.title) { + propertyValues.push({ + idShort: "title", + valueType: "xs:string", + value: propertyValue.title, + modelType: "Property", + }); + } + // observable + if (propertyValue.observable) { + propertyValues.push({ + idShort: "observable", + valueType: "xs:boolean", + value: `${propertyValue.observable}`, // in AID represented as string + modelType: "Property", + }); + } + // readOnly and writeOnly marked as EXTERNAL in AID spec + // range and others? Simply add them as is? + + // forms + if (formElementPicked) { + const propertyForm: Array = []; - // forms - if (propertyValue.forms) { - const propertyForm: Array = []; - /* for (const FormElementProperty in propertyValue.forms) { // TODO AID for now supports just *one* href/form - } */ - if (propertyValue.forms.length > 0) { - // pick first one for now (TODO change in future) - const propertyForm0 = propertyValue.forms[0]; + // --> pick the first one that matches protocol (other means in future?) + // "idShort": "htv:methodName", - if (propertyForm0.href) { + if (formElementPicked.href) { propertyForm.push({ idShort: "href", valueType: "xs:string", - value: propertyForm0.href, + value: formElementPicked.href, modelType: "Property", }); } // TODO other terms? + + propertyValues.push({ + idShort: "forms", + value: propertyForm, + modelType: "SubmodelElementCollection", + }); } - propertyValues.push({ - idShort: "forms", - value: propertyForm, + properties.push({ + idShort: propertyKey, + // TODO description + value: propertyValues, modelType: "SubmodelElementCollection", }); } + } + // Actions + if (td.actions) { + // TODO actions - TBD by AID + } - properties.push({ - idShort: propertyKey, - // TODO description - value: propertyValues, - modelType: "SubmodelElementCollection", - }); + // Events + if (td.events) { + // TODO events - TBD by AID } } const values: Array = []; - // Properties values.push({ idShort: "Properties", value: properties, modelType: "SubmodelElementCollection", }); - - // Actions - TBD by AID + // Actions values.push({ idShort: "Actions", - value: [], + value: actions, modelType: "SubmodelElementCollection", }); - - // Events - TBD by AID + // Events values.push({ idShort: "Events", - value: [], + value: events, modelType: "SubmodelElementCollection", }); diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 4e8eea79b..47f233036 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -391,7 +391,7 @@ class AssetInterfaceDescriptionUtilTest { }; @test async "should correctly transform sample TD into JSON submodel"() { - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1)); + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), "http"); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); @@ -525,13 +525,39 @@ class AssetInterfaceDescriptionUtilTest { expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); } + @test + async "should transform sample TD into JSON submodel without any properties due to unknown protocol prefix"() { + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), "unknown"); + + const smObj = JSON.parse(sm); + 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); + + // InterfaceMetadata with *no* properties for this protocol + 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); + for (const interactionValues of smValue.value) { + if (interactionValues.idShort === "Properties") { + expect(interactionValues).to.have.property("value").to.be.an("array").to.have.lengthOf(0); + } + } + } + } + expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); + } + @test async "should correctly transform sample TD into JSON AAS"() { - const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(this.td1)); + const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(this.td1), ["http"]); const aasObj = JSON.parse(sm); expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1); - // Note: proper AID submodel checks done in previous test-case + // Note: proper AID submodel checks done in previous test-cases } } From 0435a8f14d8334bbe52617a0b240b3546a75f027 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 28 Aug 2023 15:16:06 +0200 Subject: [PATCH 14/30] refactor: add TD counter transformation --- .../test/AssetInterfaceDescriptionTest.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 47f233036..169e8b8a8 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -560,4 +560,20 @@ class AssetInterfaceDescriptionUtilTest { // Note: proper AID submodel checks done in previous test-cases } + + @test async "should correctly transform counter TD into JSON AAS"() { + const response = await fetch("http://plugfest.thingweb.io:8083/counter"); + const counterTD = await response.json(); + + const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]); + + const aasObj = JSON.parse(sm); + expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); + expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(2); + + // TODO proper AID submodel checks + console.log("XXX\n\n"); + console.log(JSON.stringify(aasObj)); + console.log("\n\nXXX"); + } } From 2e24bb09f558c9b8649da6574d4279b96dbd8708 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 31 Aug 2023 16:03:08 +0200 Subject: [PATCH 15/30] refactor: add support for form terms like "contentType" and htv:methodName" --- .../src/util/asset-interface-description.ts | 22 +++++++++++-------- .../test/AssetInterfaceDescriptionTest.ts | 18 +++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index fcdcb6ed7..bbe9d6b2c 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -818,16 +818,20 @@ export class AssetInterfaceDescriptionUtil { // TODO AID for now supports just *one* href/form // --> pick the first one that matches protocol (other means in future?) - // "idShort": "htv:methodName", - if (formElementPicked.href) { - propertyForm.push({ - idShort: "href", - valueType: "xs:string", - value: formElementPicked.href, - modelType: "Property", - }); + // walk over string values like: "href", "contentType", "htv:methodName", ... + for (const formTerm in formElementPicked) { + const formValue = formElementPicked[formTerm]; + if (typeof formValue === "string") { + propertyForm.push({ + idShort: formTerm, + valueType: "xs:string", + value: formValue, + modelType: "Property", + }); + } } - // TODO other terms? + + // TODO terms that are not string-based, like op arrays? propertyValues.push({ idShort: "forms", diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 169e8b8a8..cdf9783d0 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -384,6 +384,9 @@ class AssetInterfaceDescriptionUtilTest { forms: [ { href: "stat", + contentType: "application/json", + "htv:methodName": "GET", + op: ["readproperty"], }, ], }, @@ -498,6 +501,9 @@ class AssetInterfaceDescriptionUtilTest { .to.be.an("array") .to.have.lengthOf.greaterThan(0); let hasHref = false; + let hasContentType = false; + let hasHtvMethodName = false; + // let hasOp = false; for (const formEntry of propProperty.value) { if (formEntry.idShort === "href") { hasHref = true; @@ -505,9 +511,21 @@ class AssetInterfaceDescriptionUtilTest { "stat", "https://www.example.com/stat", ]); + } else if (formEntry.idShort === "contentType") { + hasContentType = true; + expect(formEntry.value).to.equal("application/json"); + } 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); From 64e6e70b3a77ef6d38e2ace6b0706f5d8a678f3f Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 08:58:05 +0200 Subject: [PATCH 16/30] refactor: install/import fetch for Node prior to 18 --- packages/td-tools/package.json | 1 + packages/td-tools/test/AssetInterfaceDescriptionTest.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/td-tools/package.json b/packages/td-tools/package.json index 5321a7e9d..b318b5de3 100644 --- a/packages/td-tools/package.json +++ b/packages/td-tools/package.json @@ -30,6 +30,7 @@ "eslint-plugin-promise": "^5.1.0", "eslint-plugin-unused-imports": "^1.1.4", "mocha": "^9.2.2", + "node-fetch": "^3.3.2", "prettier": "^2.3.2", "stream-http": "^3.2.0", "ts-loader": "8", diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index cdf9783d0..c8fe91126 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -15,6 +15,7 @@ import { suite, test } from "@testdeck/mocha"; import { expect } from "chai"; +import fetch from "node-fetch"; import { AssetInterfaceDescriptionUtil } from "../src/util/asset-interface-description"; import { promises as fs } from "fs"; From a4d8c813b85852eb433e3878470b7d4d10217f5c Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 09:22:00 +0200 Subject: [PATCH 17/30] chore: update lock file --- package-lock.json | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/package-lock.json b/package-lock.json index 81561862c..bdb2935bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3554,6 +3554,15 @@ "node": ">=0.10" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5190,6 +5199,29 @@ "pend": "~1.2.0" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5369,6 +5401,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7825,6 +7869,25 @@ "node": ">= 6.13.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-dtls-client": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/node-dtls-client/-/node-dtls-client-0.6.0.tgz", @@ -13245,6 +13308,7 @@ "eslint-plugin-promise": "^5.1.0", "eslint-plugin-unused-imports": "^1.1.4", "mocha": "^9.2.2", + "node-fetch": "^3.3.2", "prettier": "^2.3.2", "stream-http": "^3.2.0", "ts-loader": "8", @@ -13274,6 +13338,24 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "packages/td-tools/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } } } } From d98132a06f56638a1588db88b82c183341d45f95 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 09:38:22 +0200 Subject: [PATCH 18/30] refactor: avoid adding node-fetch as dependency --- package-lock.json | 82 ------------------- packages/td-tools/package.json | 1 - .../test/AssetInterfaceDescriptionTest.ts | 4 +- 3 files changed, 2 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdb2935bd..81561862c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3554,15 +3554,6 @@ "node": ">=0.10" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5199,29 +5190,6 @@ "pend": "~1.2.0" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5401,18 +5369,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7869,25 +7825,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-dtls-client": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/node-dtls-client/-/node-dtls-client-0.6.0.tgz", @@ -13308,7 +13245,6 @@ "eslint-plugin-promise": "^5.1.0", "eslint-plugin-unused-imports": "^1.1.4", "mocha": "^9.2.2", - "node-fetch": "^3.3.2", "prettier": "^2.3.2", "stream-http": "^3.2.0", "ts-loader": "8", @@ -13338,24 +13274,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "packages/td-tools/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } } } } diff --git a/packages/td-tools/package.json b/packages/td-tools/package.json index b318b5de3..5321a7e9d 100644 --- a/packages/td-tools/package.json +++ b/packages/td-tools/package.json @@ -30,7 +30,6 @@ "eslint-plugin-promise": "^5.1.0", "eslint-plugin-unused-imports": "^1.1.4", "mocha": "^9.2.2", - "node-fetch": "^3.3.2", "prettier": "^2.3.2", "stream-http": "^3.2.0", "ts-loader": "8", diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index c8fe91126..9da932923 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -15,7 +15,6 @@ import { suite, test } from "@testdeck/mocha"; import { expect } from "chai"; -import fetch from "node-fetch"; import { AssetInterfaceDescriptionUtil } from "../src/util/asset-interface-description"; import { promises as fs } from "fs"; @@ -580,7 +579,8 @@ class AssetInterfaceDescriptionUtilTest { // Note: proper AID submodel checks done in previous test-cases } - @test async "should correctly transform counter TD into JSON AAS"() { + @test.skip async "should correctly transform counter TD into JSON AAS"() { + // built-in fetch requires Node.js 18+ const response = await fetch("http://plugfest.thingweb.io:8083/counter"); const counterTD = await response.json(); From 23898e8c8e09c2f2781ce16aa6ecf53a88c0ac61 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 17:38:14 +0200 Subject: [PATCH 19/30] refactor: wrap submodelElements in *one* AID submodel --- .../src/util/asset-interface-description.ts | 64 +++++++++---------- .../test/AssetInterfaceDescriptionTest.ts | 14 ++-- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index bbe9d6b2c..bf4e3780e 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -584,20 +584,9 @@ export class AssetInterfaceDescriptionUtil { * @returns transformed AAS in JSON format */ public transformTD2AAS(td: string, protocols: string[]): string { - const submodelObjs: unknown[] = []; - let submodelId; - - // apply selection like HTTP only or so.. - for (const protocol of protocols) { - const submodel = this.transformTD2SM(td, protocol); - const submodelObj = JSON.parse(submodel); - submodelObjs.push(submodelObj); - if (submodelId === undefined) { - submodelId = submodelObj.id; - } - } - - // TODO how should the structure w.r.t. submodel ID reference look like if there are several submodels + const submodel = this.transformTD2SM(td, protocols); + const submodelObj = JSON.parse(submodel); + const submodelId = submodelObj.id; // configuration const aasName = "SampleAAS"; @@ -625,7 +614,7 @@ export class AssetInterfaceDescriptionUtil { modelType: "AssetAdministrationShell", }, ], - submodels: submodelObjs, + submodels: [submodelObj], conceptDescriptions: [], }; @@ -636,20 +625,40 @@ export class AssetInterfaceDescriptionUtil { * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format * * @param td input TD - * @param protocol protocol prefix of interest (e.g., "http") + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) * @returns transformed AID submodel definition in JSON format */ - public transformTD2SM(tdAsString: string, protocol: string): string { + public transformTD2SM(tdAsString: string, protocols: string[]): string { const td: ThingDescription = TDParser.parseTD(tdAsString); + const aidID = td.id ? td.id : "ID_" + Math.random(); + console.log("TD " + td.title + " parsed..."); - // use protocol binding prefix like "http" for name - const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); + const submdelElements = []; + for (const protocol of protocols) { + // use protocol binding prefix like "http" for name + const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); + + const submdelElement = { + idShort: submodelElementIdShort, + // semanticId needed? + // embeddedDataSpecifications needed? + value: [ + // support + this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions + this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events + // externalDescriptor ? + ], + modelType: "SubmodelElementCollection", + }; + + submdelElements.push(submdelElement); + } const aidObject = { idShort: "AssetInterfacesDescription", - id: "TODO XYZ", + id: aidID, kind: "Instance", // semanticId needed? description: [ @@ -659,20 +668,7 @@ export class AssetInterfaceDescriptionUtil { text: td.title, // TODO should be description, where does title go to? later on in submodel? }, ], - submodelElements: [ - { - idShort: submodelElementIdShort, - // semanticId needed? - // embeddedDataSpecifications needed? - value: [ - // support - this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions - this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events - // externalDescriptor ? - ], - modelType: "SubmodelElementCollection", - }, - ], + submodelElements: submdelElements, modelType: "Submodel", }; diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 9da932923..308a3cc0b 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -394,7 +394,7 @@ class AssetInterfaceDescriptionUtilTest { }; @test async "should correctly transform sample TD into JSON submodel"() { - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), "http"); + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["http"]); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); @@ -545,7 +545,7 @@ class AssetInterfaceDescriptionUtilTest { @test async "should transform sample TD into JSON submodel without any properties due to unknown protocol prefix"() { - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), "unknown"); + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["unknown"]); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); @@ -579,7 +579,7 @@ class AssetInterfaceDescriptionUtilTest { // Note: proper AID submodel checks done in previous test-cases } - @test.skip async "should correctly transform counter TD into JSON AAS"() { + @test async "should correctly transform counter TD into JSON AAS"() { // built-in fetch requires Node.js 18+ const response = await fetch("http://plugfest.thingweb.io:8083/counter"); const counterTD = await response.json(); @@ -587,12 +587,14 @@ class AssetInterfaceDescriptionUtilTest { const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]); const aasObj = JSON.parse(sm); - expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); - expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(2); - // TODO proper AID submodel checks console.log("XXX\n\n"); console.log(JSON.stringify(aasObj)); console.log("\n\nXXX"); + + expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array"); + expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1); + const submodel = aasObj.submodels[0]; + expect(submodel).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf(2); } } From f549d1d6febf665f70d744c451fd3e9280a80a96 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 17:42:24 +0200 Subject: [PATCH 20/30] refactor: fix lint warnings --- .../src/util/asset-interface-description.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index bf4e3780e..db9f76ef7 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -55,18 +55,6 @@ interface SubmodelInformation { endpointMetadataArray: Array>; } -// TODO can/shall we define als AAS/AID contructs? Difficult since idShort and value array is used *everywhere* -interface EndpointMetadata { - idShort: "EndpointMetadata"; - value: Array; - modelType: "SubmodelElementCollection"; -} - -interface EndpointMetadataValue { - idShort: string; // base, contentType, securityDefinitions -} - -const noSecSS: SecurityScheme = { scheme: "nosec" }; const noSecName = 0 + "_sc"; export class AssetInterfaceDescriptionUtil { @@ -423,6 +411,7 @@ export class AssetInterfaceDescriptionUtil { thing.securityDefinitions = this.getSecurityDefinitionsFromEndpointMetadata(endpointMetadata); thing.security = this.getSecurityFromEndpointMetadata(endpointMetadata); // iterate over securitySchemes + // eslint-disable-next-line unused-imports/no-unused-vars for (const [key, value] of Object.entries(thing.securityDefinitions)) { // console.log(key, value); // TODO we could change the name to avoid name collisions. Shall we do so? From 76cf88cf8bdc08aaf5782e5ffd50f4ec574762d7 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 17:42:54 +0200 Subject: [PATCH 21/30] refactor: skip online counter test --- packages/td-tools/test/AssetInterfaceDescriptionTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 308a3cc0b..4a99f8cfc 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -579,7 +579,7 @@ class AssetInterfaceDescriptionUtilTest { // Note: proper AID submodel checks done in previous test-cases } - @test async "should correctly transform counter TD into JSON AAS"() { + @test.skip async "should correctly transform counter TD into JSON AAS"() { // built-in fetch requires Node.js 18+ const response = await fetch("http://plugfest.thingweb.io:8083/counter"); const counterTD = await response.json(); From 1d9cbe045f6389307b50bac9cf899b68bb021170 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 5 Sep 2023 17:47:18 +0200 Subject: [PATCH 22/30] refactor: remove out-dated samples and tests --- .../test/AssetInterfaceDescriptionTest.ts | 208 +-- packages/td-tools/test/util/AID_v03.aasx | Bin 4626 -> 0 bytes packages/td-tools/test/util/AID_v03.json | 1629 ----------------- .../td-tools/test/util/AID_v03_counter.aasx | Bin 2758 -> 0 bytes .../td-tools/test/util/AID_v03_counter.json | 276 --- 5 files changed, 1 insertion(+), 2112 deletions(-) delete mode 100644 packages/td-tools/test/util/AID_v03.aasx delete mode 100644 packages/td-tools/test/util/AID_v03.json delete mode 100644 packages/td-tools/test/util/AID_v03_counter.aasx delete mode 100644 packages/td-tools/test/util/AID_v03_counter.json diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 4a99f8cfc..b7a739437 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -24,213 +24,7 @@ import { ThingDescription } from "wot-typescript-definitions"; class AssetInterfaceDescriptionUtilTest { private assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); - @test.skip async "should correctly transform sample JSON AID_submodel HTTP v03 into a TD"() { - const modelFullString = await (await fs.readFile("test/util/AID_v03.json")).toString(); - const modelFull = JSON.parse(modelFullString); - const modelSub = modelFull.submodels[1]; - - // submodel only + HTTP only - const td = this.assetInterfaceDescriptionUtil.transformSM2TD( - JSON.stringify(modelSub), - `{"title": "myTitle", "id": "urn:uuid:3deca264-4f90-4321-a5ea-f197e6a1c7cf"}`, - "HTTP" - ); - const tdObj = JSON.parse(td); - // console.log(JSON.stringify(tdObj, null, 2)); - - // security - expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(2); - expect(tdObj.securityDefinitions[tdObj.security[0]]).to.have.property("scheme").that.equals("basic"); - expect(tdObj.securityDefinitions[tdObj.security[1]]).to.have.property("scheme").that.equals("oauth2"); - // form entries limited to 1 - expect(tdObj).to.have.property("properties").to.have.property("voltage"); - expect(tdObj) - .to.have.property("properties") - .to.have.property("voltage") - .to.have.property("forms") - .to.be.an("array") - .to.have.lengthOf(1); - } - - @test.skip async "should correctly transform sample JSON AID_v03 into a TD"() { - const modelAID = (await fs.readFile("test/util/AID_v03.json")).toString(); - const td = this.assetInterfaceDescriptionUtil.transformAAS2TD( - modelAID, - `{"title": "myTitle", "id": "urn:uuid:3deca264-4f90-4321-a5ea-f197e6a1c7cf"}` - ); - - const tdObj = JSON.parse(td); - // console.log(JSON.stringify(tdObj, null, 2)); - // TODO proper TD validation based on playground and/or JSON schema? - expect(tdObj).to.have.property("@context").that.equals("https://www.w3.org/2022/wot/td/v1.1"); - expect(tdObj).to.have.property("title").that.equals("myTitle"); - expect(tdObj).to.have.property("id").that.equals("urn:uuid:3deca264-4f90-4321-a5ea-f197e6a1c7cf"); - - expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(5); - // Security Modbus - expect(tdObj.securityDefinitions[tdObj.security[0]]).to.have.property("scheme").that.equals("nosec"); - // Security HTTP - expect(tdObj.securityDefinitions[tdObj.security[1]]).to.have.property("scheme").that.equals("basic"); - expect(tdObj.securityDefinitions[tdObj.security[1]]).to.have.property("in").that.equals("header"); - expect(tdObj.securityDefinitions[tdObj.security[2]]).to.have.property("scheme").that.equals("oauth2"); - expect(tdObj.securityDefinitions[tdObj.security[2]]).to.have.property("flow").that.equals("client"); - expect(tdObj.securityDefinitions[tdObj.security[2]]) - .to.have.property("token") - .that.equals("https://example.com/token"); - expect(tdObj.securityDefinitions[tdObj.security[2]]).to.have.property("scopes").that.equals("limited"); - // Security OPC - expect(tdObj.securityDefinitions[tdObj.security[3]]).to.have.property("scheme").that.equals("uasec"); - expect(tdObj.securityDefinitions[tdObj.security[3]]) - .to.have.property("mode") - .that.equals('["none", "Sign", "Sign & Encrypt"]'); - expect(tdObj.securityDefinitions[tdObj.security[3]]) - .to.have.property("policy") - .that.equals('["none", "Basic128RSA15", "Basic256", "Basic256SHA256"]'); - // Security MQTT - expect(tdObj.securityDefinitions[tdObj.security[4]]).to.have.property("scheme").that.equals("basic"); - expect(tdObj.securityDefinitions[tdObj.security[4]]).to.have.property("in").that.equals("header"); - - expect(tdObj).to.have.property("properties").to.have.property("voltage"); - - // form entries - expect(tdObj) - .to.have.property("properties") - .to.have.property("voltage") - .to.have.property("forms") - .to.be.an("array") - .to.have.lengthOf(4); - // Modbus - expect(tdObj.properties.voltage.forms[0]).to.have.property("href").to.eql("modbus+tcp://192.168.1.187:502"); - expect(tdObj.properties.voltage.forms[0]) - .to.have.property("contentType") - .to.eql("application/octet-stream;byteSeq=BIG_ENDIAN"); - expect(tdObj.properties.voltage.forms[0]).to.have.property("modbus:function").to.eql("readHoldingRegisters"); - expect(tdObj.properties.voltage.forms[0]).to.have.property("modbus:address").to.eql("40001"); - expect(tdObj.properties.voltage.forms[0]).to.have.property("modbus:quantity").to.eql("2"); // not a proper number in AID -> valueType *not* set - expect(tdObj.properties.voltage.forms[0]).to.have.property("security").to.deep.equal(["0_sc"]); - // HTTP - expect(tdObj.properties.voltage.forms[1]) - .to.have.property("href") - .to.eql("https://192.168.1.187" + "/properties/voltage"); - expect(tdObj.properties.voltage.forms[1]).to.have.property("htv:methodName").to.eql("GET"); - expect(tdObj.properties.voltage.forms[1]).to.have.property("contentType").to.eql("text/xml"); // Note: "application/json" overridden locally - expect(tdObj.properties.voltage.forms[1]).to.have.property("subprotocol").to.eql("longpoll"); - expect(tdObj.properties.voltage.forms[1]).to.have.property("security").to.deep.equal(["1_sc", "2_sc"]); - // OPC - expect(tdObj.properties.voltage.forms[2]) - .to.have.property("href") - .to.eql("opc.tcp://192.168.1.187:4840/UAserver"); - expect(tdObj.properties.voltage.forms[2]).to.have.property("contentType").to.eql("application/x.opcua.binary"); - expect(tdObj.properties.voltage.forms[2]).to.have.property("ua:nodeId").to.eql('"ns=3;i=29"'); - expect(tdObj.properties.voltage.forms[2]) - .to.have.property("ua:expandedNodeId") - .to.eql(' "nsu=http://example.com/OPCUAServer/energy;i=29"'); - expect(tdObj.properties.voltage.forms[2]).to.have.property("ua:method").to.eql("READ"); - expect(tdObj.properties.voltage.forms[2]).to.have.property("security").to.deep.equal(["3_sc"]); - // MQTT - expect(tdObj.properties.voltage.forms[3]).to.have.property("href").to.eql("mqtt://test.mosquitto:1884"); - expect(tdObj.properties.voltage.forms[3]).to.have.property("contentType").to.eql("application/json"); - expect(tdObj.properties.voltage.forms[3]) - .to.have.property("mqv:topic") - .to.eql("/devices/thing1/properties/voltage"); - expect(tdObj.properties.voltage.forms[3]).to.have.property("mqv:controlPacket").to.eql("mqv:subscribe"); - expect(tdObj.properties.voltage.forms[3]).to.have.property("mqv:retain").to.eql(true); // value is string but valueType states boolean - expect(tdObj.properties.voltage.forms[3]).to.have.property("security").to.deep.equal(["4_sc"]); - - // filter HTTP submodel only - const td2 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "myTitle"}`, "HTTP"); - const td2Obj = JSON.parse(td2); - // security - expect(td2Obj).to.have.property("security").to.be.an("array").to.have.lengthOf(2); - expect(td2Obj.securityDefinitions[td2Obj.security[0]]).to.have.property("scheme").that.equals("basic"); - expect(td2Obj.securityDefinitions[td2Obj.security[1]]).to.have.property("scheme").that.equals("oauth2"); - // form entries limited to 1 - expect(td2Obj).to.have.property("properties").to.have.property("voltage"); - expect(td2Obj) - .to.have.property("properties") - .to.have.property("voltage") - .to.have.property("forms") - .to.be.an("array") - .to.have.lengthOf(1); - - // filter Modbus and HTTP and submodel only - const td3 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "myTitle"}`, "Modbus|HTTP"); - const td3Obj = JSON.parse(td3); - // security - expect(td3Obj).to.have.property("security").to.be.an("array").to.have.lengthOf(3); - expect(td3Obj.securityDefinitions[td3Obj.security[0]]).to.have.property("scheme").that.equals("nosec"); - expect(td3Obj.securityDefinitions[td3Obj.security[1]]).to.have.property("scheme").that.equals("basic"); - expect(td3Obj.securityDefinitions[td3Obj.security[2]]).to.have.property("scheme").that.equals("oauth2"); - // form entries limited to 2 - expect(td3Obj).to.have.property("properties").to.have.property("voltage"); - expect(td3Obj) - .to.have.property("properties") - .to.have.property("voltage") - .to.have.property("forms") - .to.be.an("array") - .to.have.lengthOf(2); - } - - @test.skip async "should correctly transform sample JSON AID_v03 for counter into a TD"() { - const modelAID = (await fs.readFile("test/util/AID_v03_counter.json")).toString(); - const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); - - const tdObj = JSON.parse(td); - // console.log(JSON.stringify(tdObj, null, 2)); - // TODO proper TD validation based on playground and/or JSON schema? - expect(tdObj).to.have.property("@context").that.equals("https://www.w3.org/2022/wot/td/v1.1"); - expect(tdObj).to.have.property("title").that.equals("counter"); - - expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(1); - expect(tdObj.securityDefinitions[tdObj.security[0]]).to.have.property("scheme").that.equals("nosec"); - - expect(tdObj).to.have.property("properties").to.have.property("count"); - // to check if count properties has type element that is equals the value defined in AID->integer - expect(tdObj) - .to.have.property("properties") - .to.have.property("count") - .to.have.property("type") - .that.equals("integer"); - - // form entries - expect(tdObj) - .to.have.property("properties") - .to.have.property("count") - .to.have.property("forms") - .to.be.an("array") - .to.have.lengthOf(1); - // HTTP - expect(tdObj.properties.count.forms[0]) - .to.have.property("href") - .to.eql("http://plugfest.thingweb.io:8083/counter" + "/properties/count"); - expect(tdObj.properties.count.forms[0]).to.have.property("htv:methodName").to.eql("GET"); - expect(tdObj.properties.count.forms[0]).to.have.property("contentType").to.eql("application/json"); - // security not needed at form level in this case - expect(tdObj.properties.count.forms[0]).not.to.have.property("security"); - - // TODO actions and events for counter thing - - // check RegEx capability with fully qualified submodel - const td2 = this.assetInterfaceDescriptionUtil.transformAAS2TD( - modelAID, - `{"title": "counter"}`, - "InterfaceHTTP" - ); - const td2Obj = JSON.parse(td2); - expect(tdObj).to.deep.equal(td2Obj); - - // check RegEx capability with search pattern for submodel - const td3 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`, "HTTP*"); - const td3Obj = JSON.parse(td3); - expect(tdObj).to.deep.equal(td3Obj); - - // check RegEx capability with fully unknown submodel - const td4 = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`, "OPC*"); - const td4Obj = JSON.parse(td4); - expect(td4Obj).to.not.have.property("properties"); - } - - @test async "should correctly transform counterHTTP_minimal into a TD"() { + @test async "should correctly transform counterHTTP into a TD"() { const modelAID = (await fs.readFile("test/util/counterHTTP.json")).toString(); const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); diff --git a/packages/td-tools/test/util/AID_v03.aasx b/packages/td-tools/test/util/AID_v03.aasx deleted file mode 100644 index d0c3cc6daa99e89c779f1516cdfe67ecae68acaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4626 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 diff --git a/packages/td-tools/test/util/AID_v03.json b/packages/td-tools/test/util/AID_v03.json deleted file mode 100644 index efaa533c0..000000000 --- a/packages/td-tools/test/util/AID_v03.json +++ /dev/null @@ -1,1629 +0,0 @@ -{ - "assetAdministrationShells": [ - { - "hasDataSpecification": [], - "asset": { - "keys": [] - }, - "submodels": [ - { - "keys": [ - { - "type": "Submodel", - "local": true, - "value": "https://example.com/ids/sm/4333_9041_7022_4184", - "index": 0, - "idType": "IRI" - } - ] - } - ], - "conceptDictionaries": [], - "identification": { - "idType": "IRI", - "id": "https://example.com/ids/aas/7474_9002_6022_1115" - }, - "idShort": "SampleAAS", - "modelType": { - "name": "AssetAdministrationShell" - } - } - ], - "assets": [], - "submodels": [ - { - "semanticId": { - "keys": [] - }, - "qualifiers": [], - "hasDataSpecification": [], - "identification": { - "idType": "IRI", - "id": "https://example.com/ids/sm/8574_9002_6022_5297" - }, - "idShort": "AssetInterfaceDescription", - "modelType": { - "name": "Submodel" - }, - "kind": "Instance", - "submodelElements": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "modbus+tcp://192.168.1.187:502", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "application/octet-stream;byteSeq=BIG_ENDIAN", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "", - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "semanticId": { - "keys": [] - }, - "type": "modbus:function", - "valueType": "", - "valueId": { - "keys": [] - }, - "value": "readHoldingRegisters", - "modelType": { - "name": "Qualifier" - } - }, - { - "type": "modbus:address", - "valueType": "", - "value": "40001", - "modelType": { - "name": "Qualifier" - } - }, - { - "type": "modbus:quantity", - "valueType": "", - "value": "2", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "double" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "double", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "readHoldingRegisters", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:function", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "40001", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:address", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "2", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:quantity", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ] - }, - { - "semanticId": { - "keys": [] - }, - "qualifiers": [], - "hasDataSpecification": [], - "identification": { - "idType": "IRI", - "id": "https://example.com/ids/sm/4333_9041_7022_4184" - }, - "idShort": "AssetInterfaceDescription", - "modelType": { - "name": "Submodel" - }, - "kind": "Instance", - "submodelElements": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Interface1 (Modbus; s. slides from 21.6.2022)", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "modbus+tcp://192.168.1.187:502", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "application/octet-stream;byteSeq=BIG_ENDIAN", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "double", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "readHoldingRegisters", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:function", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "40001", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:address", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "2", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "modbus:quantity", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "dataMapping", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Interface2 (HTTP; s. slides from 19.7.2022)", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "https://192.168.1.187", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "application/json", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "securityDefinitions", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "basic", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "header", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "in", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "oauth2", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "client", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "flow", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "https://example.com/token", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "token", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "limited", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "scopes", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "alternativeEndpointDescriptor", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "mimeType": "application/yaml", - "value": "Device.OpenAPI.yaml", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "OpenAPI", - "modelType": { - "name": "File" - }, - "kind": "Instance" - }, - { - "mimeType": "application/td+json", - "value": "Device.td.jsonld", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "ThingDescription", - "modelType": { - "name": "File" - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "double", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "/properties/voltage", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "href", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "anyURI" - } - }, - "kind": "Instance" - }, - { - "value": "GET", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "htv:methodName", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "text/xml", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance", - "descriptions": [ - { - "language": "", - "text": "overwrites the globale definition (from endpoint metadata)" - } - ] - }, - { - "value": "longpoll", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "subprotocol", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "dataMapping", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Interface3 (OPC UA; s. slides from 2.8.2022)", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "opc.tcp://192.168.1.187:4840/UAserver", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "application/x.opcua.binary", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "securityDefinitions", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "uasec", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "[\"none\", \"Sign\", \"Sign & Encrypt\"]", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "mode", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "[\"none\", \"Basic128RSA15\", \"Basic256\", \"Basic256SHA256\"]", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "policy", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "alternativeEndpointDescriptor", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": { - "keys": [] - }, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "OPC UA Data Sheet SM", - "modelType": { - "name": "ReferenceElement" - }, - "kind": "Instance" - }, - { - "mimeType": "text/xml", - "value": "ua.nodeset.xml", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Nodeset", - "modelType": { - "name": "File" - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "double", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "\"ns=3;i=29\"", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "ua:nodeId", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": " \"nsu=http://example.com/OPCUAServer/energy;i=29\"", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "ua:expandedNodeId", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "string" - } - }, - "kind": "Instance", - "descriptions": [ - { - "language": "", - "text": "alternative to nodeId" - } - ] - }, - { - "value": "READ", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "ua:method", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "dataMapping", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Interface4 (MQTT; s. slides from 16.8.2022)", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "mqtt://test.mosquitto:1884", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "3.1.1", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "mqv:version", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "string" - } - }, - "kind": "Instance" - }, - { - "value": "application/json", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "securityDefinitions", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "basic", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "header", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "in", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "alternativeEndpointDescriptor", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "mimeType": "text/xml", - "value": "", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "OPC UA P/S MQTT Configuration File", - "modelType": { - "name": "File" - }, - "kind": "Instance" - }, - { - "mimeType": "application/td+json", - "value": "Device.td.jsonld", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "ThingDescription", - "modelType": { - "name": "File" - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "double", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "voltage", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "/devices/thing1/properties/voltage", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "mqv:topic", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "mqv:subscribe", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "mqv:controlPacket", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "true", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "mqv:retain", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "boolean" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "dataMapping", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ] - } - ], - "conceptDescriptions": [] -} diff --git a/packages/td-tools/test/util/AID_v03_counter.aasx b/packages/td-tools/test/util/AID_v03_counter.aasx deleted file mode 100644 index 1ac10693f9e98a5770e0e1fb37a457e9ed769415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2758 zcmbVO3p~?nA77X(WVt2eohdSxZP@I%w33S47g1>T+SoGN*jy%sax3H(qH;?&a-FDz zlOlu+9V8^3%ye;yRLa@vJ@3(}_ngmp|NqbPd_K?Td4AvLdA`5j^ZWiBZA8W70U`hi z!0yOCXJsp|Gl~EJKwi+m0962izzl^8J3V?J#g{^Z6eV$tUGnuIv(0| zv|V+!>eSnEwNDh_D8OIyWw8*y!spcnnNp5 zhcms&B!2=^pUxoB1T#MLKz{;DFbwpCGYH;(1YZ)|0D(Zm1*iD~1Nto{#F@RwXp%81LITVLB@HR%GjE#tB zG}6e2=mUkox0=Dfxz<)8r(b=RY7^iRYIUsJTMPhTZUF$OzxzyhWhNevCxsIH8B`M9 zo9>UN5Se(vhInHmVxx_w@#K{yqC-*b?ln4#tb= zL!ONhYeoGWaJ$BNEo#ZCAL~DU3h7tk++5V)j>tyFcV;EX7%ybb5l?lGF_Z|{BCcu8 z=Iw|=r0nqe@yVFunyr-zzh-lle?@F;o|MbMbSZuMJUQMzlm>3Rux=bPYjvk7d0=XG zQ=x_I1v8|HZ+1a)WgHtFPb@;8p=UP?ukx47ypF_0DD}W6W?J zmfa$+dfZIt(vC5p@-@>3O^sS-t6Z)}sPFbESd*Z9 zXT#}NYIg^;qIh1HeYX9JC$jZWsY_im2U#vZTq9DD@wDZ|q4$U!O3TI&I2tiyl9n2Frq^`xj5fR>(yuUh;ci zW?fCDWI{^Sx7;uMv-&?Ax(9JOkqnIw+RNsyL%;_zc| z%kFTS3ztRN@)K3>GnlBI2XCkQ5!9Hjl?eg!rz;wcha^0E)1QnH_}=7MsJU%3d?!BwDIPjm)c7~ZhS=osP~bb z=}sH-$b(j!PR5*Mxri>lcpOzZr7dx5syDTXUjkC<@0Ew#w%Q-LZ1)!Uk_4NQZU{j2 z_9eCLaT^QD^y&?Lt1)@%PG4Q_Xaq0UX#^aX(9$?lIP=JKd1C2_MO(#~D6e8Hq`Sl> z?gma;gvF-n;4VN!Hr_+q*7A{ zKd6QEO&yJjc0SB@&hFVcud;SmdBf3%JDO@MmnGL;$JcjtNLSG=X1`WOftIV?8lAZO zxcP{>7Y*&e=dMovX16i9+eSNpMH+p}MXKu$zn@xyG_NtSnn9@nhRpaM`3pS*?nmG5 zo$64YvU@65_-yY*J}1xPYH`=#yXe>WKN6u(T=GvdPK*kqhAa>lmERNLSA|)TXa81g zGE(elcOl?i<;Mn85SxQ>gI%|Vwj7S0zPD@4A@-NogSU98VKK5U#e7oM+!1=N(cJvZ z;CW+Tu8MA(*ywl;BJt-bgfe!15j9$@u~lXmc&$CgigN;#p^z75HldoQZ5JkfSr)V| zoI)c6z_J3e$<80jH1p3@#_C6G41HtS|4yQ@CCKxH^7$E;oI3~pE#d<6KYUlC7$=s* zb8Id;Ah~WD)ML$G-i%QhlO%13BR7 zQtOmcwH7yCE0NdD9d3-Jf6JK;lXtRTz?rjvGdTfg?mOr-7Kz5f3w4g^`FA;Kj{Ra4W BJRkr7 diff --git a/packages/td-tools/test/util/AID_v03_counter.json b/packages/td-tools/test/util/AID_v03_counter.json deleted file mode 100644 index 18dd7cb55..000000000 --- a/packages/td-tools/test/util/AID_v03_counter.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "assetAdministrationShells": [ - { - "hasDataSpecification": [], - "asset": { - "keys": [] - }, - "submodels": [ - { - "keys": [ - { - "type": "Submodel", - "local": true, - "value": "https://example.com/ids/sm/8153_8071_1122_3509", - "index": 0, - "idType": "IRI" - } - ] - } - ], - "conceptDictionaries": [], - "identification": { - "idType": "IRI", - "id": "https://example.com/ids/aas/7474_9002_6022_1115" - }, - "idShort": "SampleAAS", - "modelType": { - "name": "AssetAdministrationShell" - } - } - ], - "assets": [], - "submodels": [ - { - "semanticId": { - "keys": [] - }, - "qualifiers": [], - "hasDataSpecification": [], - "identification": { - "idType": "IRI", - "id": "https://example.com/ids/sm/8153_8071_1122_3509" - }, - "idShort": "AssetInterfaceDescription", - "modelType": { - "name": "Submodel" - }, - "kind": "Instance", - "submodelElements": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceHTTP", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "EndpointMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "http://plugfest.thingweb.io:8083/counter", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "base", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "value": "application/json", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "contentType", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "securityDefinitions", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "nosec", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "InterfaceMetadata", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Properties", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [ - { - "type": "valueType", - "valueType": "", - "value": "integer", - "modelType": { - "name": "Qualifier" - } - } - ], - "hasDataSpecification": [], - "idShort": "count", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [ - { - "value": "/properties/count", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "href", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "anyURI" - } - }, - "kind": "Instance" - }, - { - "value": "GET", - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "htv:methodName", - "modelType": { - "name": "Property" - }, - "valueType": { - "dataObjectType": { - "name": "" - } - }, - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Operations", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - }, - { - "ordered": false, - "allowDuplicates": false, - "semanticId": { - "keys": [] - }, - "constraints": [], - "hasDataSpecification": [], - "idShort": "Events", - "modelType": { - "name": "SubmodelElementCollection" - }, - "value": [], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ], - "kind": "Instance" - } - ] - } - ], - "conceptDescriptions": [] -} From f28dcf5dc1d93773e9bc32fb16185294d8e61f7e Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 10:07:58 +0200 Subject: [PATCH 23/30] refactor: add support for thing metadata --- .../src/util/asset-interface-description.ts | 35 +++++++++++++------ .../test/AssetInterfaceDescriptionTest.ts | 5 +-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index db9f76ef7..eb9c40192 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -34,6 +34,8 @@ const logInfo = debug(`${namespace}:info`); /* * TODOs + * - Support desription(s) and title(s) + * - transformToTD without any binding prefix (Idea: collect first all possible bindings) * - what is the desired input/output? string, object, ... ? * - what are options that would be desired? (context version, id, security, ...) -> template mechanism fine? * - Fields like @context, id, .. etc representable in AID? @@ -52,6 +54,8 @@ interface SubmodelInformation { actions: Map>; events: Map>; + thing: Map>; + endpointMetadataArray: Array>; } @@ -305,6 +309,12 @@ export class AssetInterfaceDescriptionUtil { // e.g., idShort: base , contentType, securityDefinitions, alternativeEndpointDescriptor? endpointMetadata = smValue; smInformation.endpointMetadataArray.push(endpointMetadata); + } else if (smValue.idShort === "InterfaceMetadata") { + // handled later + } else if (smValue.idShort === "externalDescriptor") { + // needed? + } else { + smInformation.thing.set(smValue.idShort, smValue.value); } } } @@ -374,6 +384,7 @@ export class AssetInterfaceDescriptionUtil { events: new Map>(), properties: new Map>(), endpointMetadataArray: [], + thing: new Map>(), }; if (aidModel instanceof Object && aidModel.submodels) { @@ -390,7 +401,16 @@ export class AssetInterfaceDescriptionUtil { private _transform(smInformation: SubmodelInformation, template?: string): string { const thing: Thing = template ? JSON.parse(template) : {}; - // TODO required fields possible in AID also? + // walk over thing information and set them + for (const [key, value] of smInformation.thing) { + if (typeof value === "string") { + thing[key] = value; + } else { + // TODO what to do with non-string values? + } + } + + // required TD fields if (!thing["@context"]) { thing["@context"] = "https://www.w3.org/2022/wot/td/v1.1"; } @@ -426,9 +446,7 @@ export class AssetInterfaceDescriptionUtil { if (smInformation.properties.size > 0) { thing.properties = {}; - for (const entry of smInformation.properties.entries()) { - const key = entry[0]; - const value: AASInteraction[] = entry[1]; + for (const [key, value] of smInformation.properties.entries()) { logInfo("Property" + key + " = " + value); thing.properties[key] = {}; @@ -480,9 +498,7 @@ export class AssetInterfaceDescriptionUtil { if (smInformation.actions.size > 0) { thing.actions = {}; - for (const entry of smInformation.actions.entries()) { - const key = entry[0]; - const value: AASInteraction[] = entry[1]; + for (const [key, value] of smInformation.actions.entries()) { logInfo("Action" + key + " = " + value); thing.actions[key] = {}; @@ -503,9 +519,7 @@ export class AssetInterfaceDescriptionUtil { if (smInformation.events.size > 0) { thing.events = {}; - for (const entry of smInformation.events.entries()) { - const key = entry[0]; - const value: AASInteraction[] = entry[1]; + for (const [key, value] of smInformation.events.entries()) { logInfo("Event " + key + " = " + value); thing.events[key] = {}; @@ -558,6 +572,7 @@ export class AssetInterfaceDescriptionUtil { events: new Map>(), properties: new Map>(), endpointMetadataArray: [], + thing: new Map>(), }; this.processSubmodel(smInformation, submodel, submodelRegex); diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index b7a739437..d84cc5464 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -26,11 +26,12 @@ class AssetInterfaceDescriptionUtilTest { @test async "should correctly transform counterHTTP into a TD"() { const modelAID = (await fs.readFile("test/util/counterHTTP.json")).toString(); - const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "counter"}`); + const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "bla"}`); const tdObj = JSON.parse(td); expect(tdObj).to.have.property("@context").that.equals("https://www.w3.org/2022/wot/td/v1.1"); - expect(tdObj).to.have.property("title").that.equals("counter"); + expect(tdObj).to.have.property("title").that.equals("Counter"); // should come form AAS + expect(tdObj).to.have.property("support").that.equals("https://github.com/eclipse-thingweb/node-wot/"); expect(tdObj).to.have.property("securityDefinitions").to.be.an("object"); From c69a1a889472c676513c7c54ecb868b12bbf571b Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 11:00:19 +0200 Subject: [PATCH 24/30] refactor: transform AAS description -> TD descriptions --- .../src/util/asset-interface-description.ts | 84 +++++++++++++------ .../test/AssetInterfaceDescriptionTest.ts | 5 ++ 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index eb9c40192..7d0c39880 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -453,35 +453,65 @@ export class AssetInterfaceDescriptionUtil { thing.properties[key].forms = []; for (const vi of value) { - // The first block of if condition is expected to be temporary. will be adjusted or removed when a decision on how the datapoint's datatype would be modelled is made for AID. - if (vi.interaction.value && vi.interaction.value instanceof Array) { - for (const interactionValue of vi.interaction.value) - if (interactionValue.idShort === "type") { - if (interactionValue.value === "float") { - thing.properties[key].type = "number"; - } else { - thing.properties[key].type = interactionValue.value; - } - } else if (interactionValue.idShort === "range") { - if (interactionValue.min) { - thing.properties[key].min = interactionValue.min; - } - if (interactionValue.max) { - thing.properties[key].max = interactionValue.max; + for (const keyInteraction in vi.interaction) { + if (keyInteraction === "description") { + const aasDescription = vi.interaction[keyInteraction]; + // convert + // + // [{ + // "language": "en", + // "text": "Current counter value" + // }, + // { + // "language": "de", + // "text": "Derzeitiger Zählerwert" + // }] + // + // to + // + // {"en": "Current counter value", "de": "Derzeitiger Zählerwert"} + const tdDescription: Record = {}; + if (aasDescription instanceof Array) { + for (const aasDescriptionEntry of aasDescription) { + if (aasDescriptionEntry.language && aasDescriptionEntry.text) { + const language: string = aasDescriptionEntry.language; + const text: string = aasDescriptionEntry.text; + tdDescription[language] = text; + } } - } else if (interactionValue.idShort === "observable") { - thing.properties[key].observable = interactionValue.value === "true"; - } else if (interactionValue.idShort === "readOnly") { - thing.properties[key].readOnly = interactionValue.value === "true"; - } else if (interactionValue.idShort === "writeOnly") { - thing.properties[key].writeOnly = interactionValue.value === "true"; - } else if (interactionValue.idShort === "forms") { - // will be handled below - } else { - // handle other terms specifically? - const key2 = interactionValue.idShort; - thing.properties[key][key2] = interactionValue.value; } + thing.properties[key].descriptions = tdDescription; + } else if (keyInteraction === "value") { + if (vi.interaction.value instanceof Array) { + for (const interactionValue of vi.interaction.value) + if (interactionValue.idShort === "type") { + if (interactionValue.value === "float") { + thing.properties[key].type = "number"; + } else { + thing.properties[key].type = interactionValue.value; + } + } else if (interactionValue.idShort === "range") { + if (interactionValue.min) { + thing.properties[key].min = interactionValue.min; + } + if (interactionValue.max) { + thing.properties[key].max = interactionValue.max; + } + } else if (interactionValue.idShort === "observable") { + thing.properties[key].observable = interactionValue.value === "true"; + } else if (interactionValue.idShort === "readOnly") { + thing.properties[key].readOnly = interactionValue.value === "true"; + } else if (interactionValue.idShort === "writeOnly") { + thing.properties[key].writeOnly = interactionValue.value === "true"; + } else if (interactionValue.idShort === "forms") { + // will be handled below + } else { + // handle other terms specifically? + const key2 = interactionValue.idShort; + thing.properties[key][key2] = interactionValue.value; + } + } + } } if (vi.endpointMetadata) { diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index d84cc5464..3673526e6 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -40,6 +40,11 @@ class AssetInterfaceDescriptionUtilTest { // check count property expect(tdObj).to.have.property("properties").to.have.property("count"); + expect(tdObj).to.have.property("properties").to.have.property("count").to.have.property("descriptions").to.eql({ + en: "Current counter value", + de: "Derzeitiger Zählerwert", + it: "Valore attuale del contatore", + }); expect(tdObj) .to.have.property("properties") .to.have.property("count") From 6b8fffbc231ede062e005d88ce38f61d36149d98 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 11:35:52 +0200 Subject: [PATCH 25/30] refactor: support title and descriptions --- .../src/util/asset-interface-description.ts | 32 ++++++++++++++++--- .../test/AssetInterfaceDescriptionTest.ts | 28 +++++++++++++++- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 7d0c39880..946e37000 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -28,13 +28,12 @@ const logInfo = debug(`${namespace}:info`); /** Utilities around Asset Interface Description * https://github.com/admin-shell-io/submodel-templates/tree/main/development/Asset%20Interface%20Description/1/0 * - * e.g, transform to TD + * e.g, transform AAS (or AID submodel) to TD or vicerversa transform TD to AAS (or AID submodel) * */ /* * TODOs - * - Support desription(s) and title(s) * - transformToTD without any binding prefix (Idea: collect first all possible bindings) * - what is the desired input/output? string, object, ... ? * - what are options that would be desired? (context version, id, security, ...) -> template mechanism fine? @@ -679,7 +678,13 @@ export class AssetInterfaceDescriptionUtil { // semanticId needed? // embeddedDataSpecifications needed? value: [ - // support + { + idShort: "title", + valueType: "xs:string", + value: td.title, + modelType: "Property", + }, + // support and other? this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events // externalDescriptor ? @@ -870,9 +875,28 @@ export class AssetInterfaceDescriptionUtil { }); } + let description; + if (propertyValue.descriptions) { + description = []; + for (const langKey in propertyValue.descriptions) { + const langValue = propertyValue.descriptions[langKey]; + description.push({ + language: langKey, + text: langValue, + }); + } + } else if (propertyValue.description) { + // fallback + description = []; + description.push({ + language: "en", // TODO where to get language identifier + text: propertyValue.description, + }); + } + properties.push({ idShort: propertyKey, - // TODO description + description: description, value: propertyValues, modelType: "SubmodelElementCollection", }); diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 3673526e6..048f8be26 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -181,6 +181,10 @@ class AssetInterfaceDescriptionUtilTest { status: { type: "string", observable: true, + descriptions: { + en: "Statistic", + de: "Statistik", + }, forms: [ { href: "stat", @@ -201,9 +205,13 @@ class AssetInterfaceDescriptionUtilTest { 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 === "EndpointMetadata") { + if (smValue.idShort === "title") { + hasThingTitle = true; + expect(smValue).to.have.property("value").to.equal("testTD"); + } 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); @@ -257,6 +265,7 @@ class AssetInterfaceDescriptionUtilTest { expect(hasSecurityDefinitions).to.equal(true); } } + expect(hasThingTitle, "No thing title").to.equal(true); expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); // InterfaceMetadata with properties etc @@ -274,6 +283,7 @@ class AssetInterfaceDescriptionUtilTest { .to.be.an("array") .to.have.lengthOf.greaterThan(0); let hasPropertyStatus = false; + let hasPropertyStatusDescription = false; for (const propertyValue of interactionValues.value) { if (propertyValue.idShort === "status") { hasPropertyStatus = true; @@ -333,8 +343,24 @@ class AssetInterfaceDescriptionUtilTest { expect(hasObservable).to.equal(true); expect(hasForms).to.equal(true); } + if (propertyValue.description) { + hasPropertyStatusDescription = true; + expect(propertyValue) + .to.have.property("description") + .to.eql([ + { + language: "en", + text: "Statistic", + }, + { + language: "de", + text: "Statistik", + }, + ]); + } } expect(hasPropertyStatus).to.equal(true); + expect(hasPropertyStatusDescription).to.equal(true); } } expect(hasProperties).to.equal(true); From 7a6a3bf425800e2d75b2288a7de3d2f3d2dfa78c Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 14:26:33 +0200 Subject: [PATCH 26/30] refactor: remove commented code --- .../src/util/asset-interface-description.ts | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 946e37000..9feb8ea69 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -107,62 +107,10 @@ export class AssetInterfaceDescriptionUtil { const ss: SecurityScheme = { scheme: securityDefinitionsValue.value }; securityDefinitions[securityDefinitionsValues.idShort] = ss; } - /* if (securityDefinitionsValue.value && securityDefinitionsValue.value instanceof Array) { - for (const secValue of securityDefinitionsValue.value) { - // allow all *other* security schemes like "uasec" as well - const ss: SecurityScheme = { scheme: secValue.idShort }; - securitySchemes.push(ss); - / * if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { - const ss : SecurityScheme = { scheme: secValue.idShort}; - securitySchemes.push(ss); - } * / - if (secValue.value && secValue.value instanceof Array) { - for (const v of secValue.value) { - if ( - v.idShort && - typeof v.idShort === "string" && - v.idShort.length > 0 && - v.value - ) { - ss[v.idShort] = v.value; - } - } - } - } - } */ } } } - /* securityDefinitions[securityDefinitionsValue.idShort] = { - - } */ } - - /* if (securityDefinitionsValue.idShort === "scheme") { - if (securityDefinitionsValue.value && securityDefinitionsValue.value instanceof Array) { - for (const secValue of securityDefinitionsValue.value) { - // allow all *other* security schemes like "uasec" as well - const ss: SecurityScheme = { scheme: secValue.idShort }; - securitySchemes.push(ss); - / * if (secValue.idShort === "nosec" || secValue.idShort === "auto" || secValue.idShort === "combo" || secValue.idShort === "basic" || secValue.idShort === "digest" || secValue.idShort === "apikey" || secValue.idShort === "bearer" || secValue.idShort === "psk" || secValue.idShort === "oauth2" ) { - const ss : SecurityScheme = { scheme: secValue.idShort}; - securitySchemes.push(ss); - } * / - if (secValue.value && secValue.value instanceof Array) { - for (const v of secValue.value) { - if ( - v.idShort && - typeof v.idShort === "string" && - v.idShort.length > 0 && - v.value - ) { - ss[v.idShort] = v.value; - } - } - } - } - } - } */ } } } From 2765ddebc8c40bda0625050a757b9ca1fd913c68 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 14:39:26 +0200 Subject: [PATCH 27/30] refactor: move public (important methods) upfront --- .../src/util/asset-interface-description.ts | 337 +++++++++--------- 1 file changed, 173 insertions(+), 164 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 9feb8ea69..e0252b648 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -24,8 +24,10 @@ import { FormElementBase, PropertyElement } from "wot-thing-model-types"; const namespace = "node-wot:td-tools:asset-interface-description-util"; const logDebug = debug(`${namespace}:debug`); const logInfo = debug(`${namespace}:info`); +const logError = debug(`${namespace}:error`); -/** Utilities around Asset Interface Description +/** + * Utilities around Asset Interface Description * https://github.com/admin-shell-io/submodel-templates/tree/main/development/Asset%20Interface%20Description/1/0 * * e.g, transform AAS (or AID submodel) to TD or vicerversa transform TD to AAS (or AID submodel) @@ -42,25 +44,159 @@ const logInfo = debug(`${namespace}:info`); * */ -interface AASInteraction { - endpointMetadata?: Record; - secNamesForEndpoint?: Array; - interaction: Record; -} +export class AssetInterfaceDescriptionUtil { + /** @deprecated use transformAAS2TD method instead */ + public transformToTD(aid: string, template?: string, submodelRegex?: string): string { + return this.transformAAS2TD(aid, template, submodelRegex); + } -interface SubmodelInformation { - properties: Map>; - actions: Map>; - events: Map>; + /** + * Transform AAS in JSON format to a WoT ThingDescription (TD) + * + * @param aas input AAS in JSON format + * @param template TD template with basic desired TD template + * @param submodelRegex allows to filter submodel elements based on regex expression (e.g, "HTTP*") or full text based on idShort (e.g., "InterfaceHTTP") + * @returns transformed TD + */ + public transformAAS2TD(aas: string, template?: string, submodelRegex?: string): string { + const smInformation = this.getSubmodelInformation(aas, submodelRegex); + return this._transform(smInformation, template); + } - thing: Map>; + /** + * Transform AID submodel definition in JSON format to a WoT ThingDescription (TD) + * + * @param aid input AID submodel in JSON format + * @param template TD template with basic desired TD template + * @param submodelRegex allows to filter submodel elements based on regex expression (e.g, "HTTP*") or full text based on idShort (e.g., "InterfaceHTTP") + * @returns transformed TD + */ + public transformSM2TD(aid: string, template?: string, submodelRegex?: string): string { + const submodel = JSON.parse(aid); - endpointMetadataArray: Array>; -} + const smInformation: SubmodelInformation = { + actions: new Map>(), + events: new Map>(), + properties: new Map>(), + endpointMetadataArray: [], + thing: new Map>(), + }; -const noSecName = 0 + "_sc"; + this.processSubmodel(smInformation, submodel, submodelRegex); + + return this._transform(smInformation, template); + } + + /** + * Transform WoT ThingDescription (TD) to AAS in JSON format + * + * @param td input TD + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) + * @returns transformed AAS in JSON format + */ + public transformTD2AAS(td: string, protocols: string[]): string { + const submodel = this.transformTD2SM(td, protocols); + const submodelObj = JSON.parse(submodel); + const submodelId = submodelObj.id; + + // configuration + const aasName = "SampleAAS"; + const aasId = "https://example.com/ids/aas/7474_9002_6022_1115"; + + const aas = { + assetAdministrationShells: [ + { + idShort: aasName, + id: aasId, + assetInformation: { + assetKind: "Type", + }, + submodels: [ + { + type: "ModelReference", + keys: [ + { + type: "Submodel", + value: submodelId, + }, + ], + }, + ], + modelType: "AssetAdministrationShell", + }, + ], + submodels: [submodelObj], + conceptDescriptions: [], + }; + + return JSON.stringify(aas); + } + + /** + * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format + * + * @param td input TD + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) + * @returns transformed AID submodel definition in JSON format + */ + public transformTD2SM(tdAsString: string, protocols: string[]): string { + const td: ThingDescription = TDParser.parseTD(tdAsString); + + const aidID = td.id ? td.id : "ID_" + Math.random(); + + console.log("TD " + td.title + " parsed..."); + + const submdelElements = []; + for (const protocol of protocols) { + // use protocol binding prefix like "http" for name + const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); + + const submdelElement = { + idShort: submodelElementIdShort, + // semanticId needed? + // embeddedDataSpecifications needed? + value: [ + { + idShort: "title", + valueType: "xs:string", + value: td.title, + modelType: "Property", + }, + // support and other? + this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions + this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events + // externalDescriptor ? + ], + modelType: "SubmodelElementCollection", + }; + + submdelElements.push(submdelElement); + } + + const aidObject = { + idShort: "AssetInterfacesDescription", + id: aidID, + kind: "Instance", + // semanticId needed? + description: [ + // TODO does this need to be an array or can it simply be a value + { + language: "en", + text: td.title, // TODO should be description, where does title go to? later on in submodel? + }, + ], + submodelElements: submdelElements, + modelType: "Submodel", + }; + + return JSON.stringify(aidObject); + } + + /* + * PRIVATE IMPLEMENTATION METHODS ARE FOLLOWING + * + */ -export class AssetInterfaceDescriptionUtil { private getBaseFromEndpointMetadata(endpointMetadata?: Record): string { if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { for (const v of endpointMetadata.value) { @@ -145,16 +281,20 @@ export class AssetInterfaceDescriptionUtil { href: this.getBaseFromEndpointMetadata(vi.endpointMetadata), contentType: this.getContentTypeFromEndpointMetadata(vi.endpointMetadata), }; - // need to add security at form level at all ? + if (addSecurity) { + // XXX need to add security at form level at all ? + logError("security at form level not added/present"); + /* const securitySchemes = this.getSecurityDefinitionsFromEndpointMetadata(vi.endpointMetadata); if (securitySchemes === undefined) { - form.security = [noSecName]; + form.security = [0 + "_sc"]; } else { if (vi.secNamesForEndpoint) { form.security = vi.secNamesForEndpoint as [string, ...string[]]; } } + */ } if (vi.interaction.value instanceof Array) { for (const iv of vi.interaction.value) { @@ -515,153 +655,6 @@ export class AssetInterfaceDescriptionUtil { return JSON.stringify(thing); } - /** @deprecated use transformAAS2TD method instead */ - public transformToTD(aid: string, template?: string, submodelRegex?: string): string { - return this.transformAAS2TD(aid, template, submodelRegex); - } - - /** - * Transform AAS in JSON format to a WoT ThingDescription (TD) - * - * @param aas input AAS in JSON format - * @param template TD template with basic desired TD template - * @param submodelRegex allows to filter submodel elements based on regex expression (e.g, "HTTP*") or full text based on idShort (e.g., "InterfaceHTTP") - * @returns transformed TD - */ - public transformAAS2TD(aas: string, template?: string, submodelRegex?: string): string { - const smInformation = this.getSubmodelInformation(aas, submodelRegex); - return this._transform(smInformation, template); - } - - /** - * Transform AID submodel definition in JSON format to a WoT ThingDescription (TD) - * - * @param aid input AID submodel in JSON format - * @param template TD template with basic desired TD template - * @param submodelRegex allows to filter submodel elements based on regex expression (e.g, "HTTP*") or full text based on idShort (e.g., "InterfaceHTTP") - * @returns transformed TD - */ - public transformSM2TD(aid: string, template?: string, submodelRegex?: string): string { - const submodel = JSON.parse(aid); - - const smInformation: SubmodelInformation = { - actions: new Map>(), - events: new Map>(), - properties: new Map>(), - endpointMetadataArray: [], - thing: new Map>(), - }; - - this.processSubmodel(smInformation, submodel, submodelRegex); - - return this._transform(smInformation, template); - } - - /** - * Transform WoT ThingDescription (TD) to AAS in JSON format - * - * @param td input TD - * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) - * @returns transformed AAS in JSON format - */ - public transformTD2AAS(td: string, protocols: string[]): string { - const submodel = this.transformTD2SM(td, protocols); - const submodelObj = JSON.parse(submodel); - const submodelId = submodelObj.id; - - // configuration - const aasName = "SampleAAS"; - const aasId = "https://example.com/ids/aas/7474_9002_6022_1115"; - - const aas = { - assetAdministrationShells: [ - { - idShort: aasName, - id: aasId, - assetInformation: { - assetKind: "Type", - }, - submodels: [ - { - type: "ModelReference", - keys: [ - { - type: "Submodel", - value: submodelId, - }, - ], - }, - ], - modelType: "AssetAdministrationShell", - }, - ], - submodels: [submodelObj], - conceptDescriptions: [], - }; - - return JSON.stringify(aas); - } - - /** - * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format - * - * @param td input TD - * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) - * @returns transformed AID submodel definition in JSON format - */ - public transformTD2SM(tdAsString: string, protocols: string[]): string { - const td: ThingDescription = TDParser.parseTD(tdAsString); - - const aidID = td.id ? td.id : "ID_" + Math.random(); - - console.log("TD " + td.title + " parsed..."); - - const submdelElements = []; - for (const protocol of protocols) { - // use protocol binding prefix like "http" for name - const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); - - const submdelElement = { - idShort: submodelElementIdShort, - // semanticId needed? - // embeddedDataSpecifications needed? - value: [ - { - idShort: "title", - valueType: "xs:string", - value: td.title, - modelType: "Property", - }, - // support and other? - this.createEndpointMetadata(td), // EndpointMetadata like base, security and securityDefinitions - this.createInterfaceMetadata(td, protocol), // InterfaceMetadata like properties, actions and events - // externalDescriptor ? - ], - modelType: "SubmodelElementCollection", - }; - - submdelElements.push(submdelElement); - } - - const aidObject = { - idShort: "AssetInterfacesDescription", - id: aidID, - kind: "Instance", - // semanticId needed? - description: [ - // TODO does this need to be an array or can it simply be a value - { - language: "en", - text: td.title, // TODO should be description, where does title go to? later on in submodel? - }, - ], - submodelElements: submdelElements, - modelType: "Submodel", - }; - - return JSON.stringify(aidObject); - } - private createEndpointMetadata(td: ThingDescription): Record { const values: Array = []; @@ -892,3 +885,19 @@ export class AssetInterfaceDescriptionUtil { return interfaceMetadata; } } + +interface AASInteraction { + endpointMetadata?: Record; + secNamesForEndpoint?: Array; + interaction: Record; +} + +interface SubmodelInformation { + properties: Map>; + actions: Map>; + events: Map>; + + thing: Map>; + + endpointMetadataArray: Array>; +} From 186acaff70596ce168682ddcb00a4e66d240f767 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 15:16:42 +0200 Subject: [PATCH 28/30] refactor: allow TD to AID transformation without specifying protocol prefixes -> use *all* possible prefixes --- .../src/util/asset-interface-description.ts | 66 ++++++++++++++----- .../test/AssetInterfaceDescriptionTest.ts | 10 ++- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index e0252b648..2bcdb4051 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -34,16 +34,6 @@ const logError = debug(`${namespace}:error`); * */ -/* - * TODOs - * - transformToTD without any binding prefix (Idea: collect first all possible bindings) - * - what is the desired input/output? string, object, ... ? - * - what are options that would be desired? (context version, id, security, ...) -> template mechanism fine? - * - Fields like @context, id, .. etc representable in AID? - * - More test-data for action & events, data input and output, ... - * - */ - export class AssetInterfaceDescriptionUtil { /** @deprecated use transformAAS2TD method instead */ public transformToTD(aid: string, template?: string, submodelRegex?: string): string { @@ -91,10 +81,10 @@ export class AssetInterfaceDescriptionUtil { * Transform WoT ThingDescription (TD) to AAS in JSON format * * @param td input TD - * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) or optional if all * @returns transformed AAS in JSON format */ - public transformTD2AAS(td: string, protocols: string[]): string { + public transformTD2AAS(td: string, protocols?: string[]): string { const submodel = this.transformTD2SM(td, protocols); const submodelObj = JSON.parse(submodel); const submodelId = submodelObj.id; @@ -136,16 +126,21 @@ export class AssetInterfaceDescriptionUtil { * Transform WoT ThingDescription (TD) to AID submodel definition in JSON format * * @param td input TD - * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) + * @param protocols protocol prefixes of interest (e.g., ["http", "coap"]) or optional if all * @returns transformed AID submodel definition in JSON format */ - public transformTD2SM(tdAsString: string, protocols: string[]): string { + public transformTD2SM(tdAsString: string, protocols?: string[]): string { const td: ThingDescription = TDParser.parseTD(tdAsString); - const aidID = td.id ? td.id : "ID_" + Math.random(); + const aidID = td.id ? td.id : "ID" + Math.random(); console.log("TD " + td.title + " parsed..."); + // collect all possible prefixes + if (protocols === undefined || protocols.length === 0) { + protocols = this.getProtocolPrefixes(td); + } + const submdelElements = []; for (const protocol of protocols) { // use protocol binding prefix like "http" for name @@ -197,6 +192,47 @@ export class AssetInterfaceDescriptionUtil { * */ + private getProtocolPrefixes(td: ThingDescription): string[] { + const protocols: string[] = []; + + if (td.properties) { + for (const propertyKey in td.properties) { + const property = td.properties[propertyKey]; + this.updateProtocolPrefixes(property.forms, protocols); + } + } + if (td.actions) { + for (const actionKey in td.actions) { + const action = td.actions[actionKey]; + this.updateProtocolPrefixes(action.forms, protocols); + } + } + if (td.events) { + for (const eventKey in td.events) { + const event = td.events[eventKey]; + this.updateProtocolPrefixes(event.forms, protocols); + } + } + + return protocols; + } + + private updateProtocolPrefixes(forms: [FormElementBase, ...FormElementBase[]], protocols: string[]): void { + if (forms) { + for (const interactionForm of forms) { + if (interactionForm.href) { + const positionColon = interactionForm.href.indexOf(":"); + if (positionColon > 0) { + const prefix = interactionForm.href.substring(0, positionColon); + if (!protocols.includes(prefix)) { + protocols.push(prefix); + } + } + } + } + } + } + private getBaseFromEndpointMetadata(endpointMetadata?: Record): string { if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { for (const v of endpointMetadata.value) { diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 048f8be26..bbe390ebc 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -198,7 +198,7 @@ class AssetInterfaceDescriptionUtilTest { }; @test async "should correctly transform sample TD into JSON submodel"() { - const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["http"]); + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["https"]); const smObj = JSON.parse(sm); expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription"); @@ -367,6 +367,14 @@ class AssetInterfaceDescriptionUtilTest { } } expect(hasInterfaceMetadata, "No InterfaceMetadata").to.equal(true); + + // Test to use all possible prefixes -> in this case it is only https + const sm2 = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1)); + const sm2Obj = JSON.parse(sm2); + // Note: id is autogenerated and needs to be exluded/removed + delete smObj.id; + delete sm2Obj.id; + expect(smObj).to.eql(sm2Obj); } @test From c06c67cfb58c7e1db5725226b136cb3423de0d85 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Thu, 7 Sep 2023 15:19:24 +0200 Subject: [PATCH 29/30] refactor: remove console.log with logInfo --- packages/td-tools/src/util/asset-interface-description.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index 2bcdb4051..ec8504e79 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -134,7 +134,7 @@ export class AssetInterfaceDescriptionUtil { const aidID = td.id ? td.id : "ID" + Math.random(); - console.log("TD " + td.title + " parsed..."); + logInfo("TD " + td.title + " parsed..."); // collect all possible prefixes if (protocols === undefined || protocols.length === 0) { @@ -556,7 +556,6 @@ export class AssetInterfaceDescriptionUtil { // iterate over securitySchemes // eslint-disable-next-line unused-imports/no-unused-vars for (const [key, value] of Object.entries(thing.securityDefinitions)) { - // console.log(key, value); // TODO we could change the name to avoid name collisions. Shall we do so? secNames.push(key); } From 9de4893fea5ae2c4ec53460e00bdb9a2a83a6328 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Mon, 11 Sep 2023 17:46:45 +0200 Subject: [PATCH 30/30] docs: update readme --- packages/td-tools/src/util/README.md | 45 +++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/td-tools/src/util/README.md b/packages/td-tools/src/util/README.md index 3edf9053b..00f139f27 100644 --- a/packages/td-tools/src/util/README.md +++ b/packages/td-tools/src/util/README.md @@ -4,9 +4,7 @@ The [IDTA Asset Interface Description (AID) working group](https://github.com/admin-shell-io/submodel-templates/tree/main/development/Asset%20Interface%20Description/1/0) defines a submodel that can be used to describe the asset's service interface or asset's related service interfaces. The current AID working assumptions reuse existing definitions from [WoT Thing Descriptions](https://www.w3.org/TR/wot-thing-description11/) and hence it is possible to consume AAS with AID definitions with node-wot (e.g., read/subscribe live data of the asset and/or associated service). -### Sample Application - -The file `AID_v03_counter.json` describes the counter sample in AID format. The `AssetInterfaceDescriptionUtil` utility class allows to transform the AID format to a valid WoT TD format which in the end can be properly consumed by node-wot. +### Sample Applications #### Prerequisites @@ -14,12 +12,14 @@ The file `AID_v03_counter.json` describes the counter sample in AID format. The - `npm install @node-wot/core` - `npm install @node-wot/binding-http` -#### Sample Code +#### AAS/AID to WoT TD + +The file `counterHTTP.json` describes the counter sample in AID format for http binding. The `AssetInterfaceDescriptionUtil` utility class allows to transform the AID format to a valid WoT TD format which in the end can be properly consumed by node-wot. The example tries to load an AID file in AID format and transforms it to a regular WoT TD. ```js -// aid-client.js +// aid-to-td.js const fs = require("fs/promises"); // to read JSON file in AID format Servient = require("@node-wot/core").Servient; @@ -63,10 +63,39 @@ async function example() { example(); ``` -#### Run the sample script +#### WoT TD to AAS/AID + +The example tries to load online counter TD and converts it to AAS JSON format. -`node aid-client.js` +```js +// td-to-aid.js +AssetInterfaceDescriptionUtil = require("@node-wot/td-tools").AssetInterfaceDescriptionUtil; -It will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count +let assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); +async function example() { + try { + const response = await fetch("http://plugfest.thingweb.io:8083/counter"); + const counterTD = await response.json(); + + const sm = assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]); + + // print JSON format of AAS + console.log(sm); + } catch (err) { + console.log(err); + } +} + +// launch example +example(); +``` + +#### Run the sample scripts + +`node aid-to-td.js` +... will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count Note: make sure that the file `counterHTTP.json` is in the same folder as the script. + +`node td-to-aid.js` +... will show the online counter im AAS/AID JSON format (usable by AASX Package Explorer 2023-08-01.alpha).