From 03751a91944a0c1f1f465078b6c91e7d7456f4f7 Mon Sep 17 00:00:00 2001 From: danielpeintner Date: Tue, 12 Sep 2023 08:04:43 +0200 Subject: [PATCH] AAS next (#1052) feat: align/improve AAS/AID <-> TD transformation * fix: issue with using wrong arguments * docs: update readme with the preferred way of using the interface * refactor: initial test (setup) * feat: update transformation to latest AID version Note: examples AID_v03.json and AID_v03_counter.json are no longer compliant -> skipped for now * refactor: initial setup for transforming TD to AAS/AID * refactor: renaming * refactor: add transformation code to * refactor: simple interaction checks for TD to AID * refactor: handle new security/securityDefinititions by @Kaz040 * refactor: restructure test input * fix: add terms needed by AASX Explorer * feat: add ability to choose protocol prefix of interest * refactor: add TD counter transformation * refactor: add support for form terms like "contentType" and htv:methodName" * refactor: install/import fetch for Node prior to 18 * chore: update lock file * refactor: avoid adding node-fetch as dependency * refactor: wrap submodelElements in *one* AID submodel * refactor: fix lint warnings * refactor: skip online counter test * refactor: remove out-dated samples and tests * refactor: add support for thing metadata * refactor: transform AAS description -> TD descriptions * refactor: support title and descriptions * refactor: remove commented code * refactor: move public (important methods) upfront * refactor: allow TD to AID transformation without specifying protocol prefixes -> use *all* possible prefixes * refactor: remove console.log with logInfo * docs: update readme --- packages/td-tools/src/util/README.md | 57 +- .../src/util/asset-interface-description.ts | 794 ++++++-- .../test/AssetInterfaceDescriptionTest.ts | 515 ++++-- 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 --- packages/td-tools/test/util/counterHTTP.aasx | Bin 0 -> 3099 bytes packages/td-tools/test/util/counterHTTP.json | 528 ++++++ 9 files changed, 1557 insertions(+), 2242 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 create mode 100644 packages/td-tools/test/util/counterHTTP.aasx create mode 100644 packages/td-tools/test/util/counterHTTP.json diff --git a/packages/td-tools/src/util/README.md b/packages/td-tools/src/util/README.md index 3cc00a9ce..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; @@ -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("counterHTTP.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(); @@ -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. + +```js +// td-to-aid.js +AssetInterfaceDescriptionUtil = require("@node-wot/td-tools").AssetInterfaceDescriptionUtil; + +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(); +``` -`node aid-client.js` +#### Run the sample scripts -It will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count +`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. -Note: make sure that the file `AID_v03_counter.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). diff --git a/packages/td-tools/src/util/asset-interface-description.ts b/packages/td-tools/src/util/asset-interface-description.ts index d2ae2d06c..ec8504e79 100644 --- a/packages/td-tools/src/util/asset-interface-description.ts +++ b/packages/td-tools/src/util/asset-interface-description.ts @@ -16,46 +16,223 @@ 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"; +import { ThingDescription } from "wot-typescript-definitions"; +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 to TD + * e.g, transform AAS (or AID submodel) to TD or vicerversa transform TD to AAS (or AID submodel) * */ -/* - * TODOs - * - 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 { + return this.transformAAS2TD(aid, template, submodelRegex); + } -interface AASInteraction { - endpointMetadata?: Record; - secNamesForEndpoint?: Array; - interaction: Record; -} + /** + * 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); + } -interface SubmodelInformation { - properties: Map>; - actions: Map>; - events: 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 noSecSS: SecurityScheme = { scheme: "nosec" }; -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"]) or optional if all + * @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"]) or optional if all + * @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(); + + logInfo("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 + 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 + * + */ + + 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); + } + } + } + } + } + } -export class AssetInterfaceDescriptionUtil { private getBaseFromEndpointMetadata(endpointMetadata?: Record): string { if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { for (const v of endpointMetadata.value) { @@ -80,36 +257,59 @@ 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 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 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; + } + } } } } } } - return securitySchemes; } } } - return undefined; + 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 security as string | [string, ...string[]]; } private createInteractionForm(vi: AASInteraction, addSecurity: boolean): TD.Form { @@ -117,64 +317,75 @@ export class AssetInterfaceDescriptionUtil { href: this.getBaseFromEndpointMetadata(vi.endpointMetadata), contentType: this.getContentTypeFromEndpointMetadata(vi.endpointMetadata), }; - // need to add security at form level at all ? + if (addSecurity) { - const securitySchemes = this.getSecuritySchemesFromEndpointMetadata(vi.endpointMetadata); + // 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 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 ? + } + } + } } } } @@ -189,7 +400,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) { @@ -221,6 +432,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); } } } @@ -290,6 +507,7 @@ export class AssetInterfaceDescriptionUtil { events: new Map>(), properties: new Map>(), endpointMetadataArray: [], + thing: new Map>(), }; if (aidModel instanceof Object && aidModel.submodels) { @@ -306,7 +524,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"; } @@ -316,39 +543,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 + // eslint-disable-next-line unused-imports/no-unused-vars + for (const [key, value] of Object.entries(thing.securityDefinitions)) { + // 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 @@ -356,25 +568,72 @@ 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] = {}; 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.constraints && vi.interaction.constraints instanceof Array) { - for (const constraint of vi.interaction.constraints) - if (constraint.type === "valueType") { - if (constraint.value === "float") { - thing.properties[key].type = "number"; - } else { - thing.properties[key].type = constraint.value; + 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; + } } } + 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) { @@ -391,9 +650,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] = {}; @@ -414,9 +671,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] = {}; @@ -435,44 +690,249 @@ 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, submodelRegex); - } + 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", + }); + } - /** - * 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); + // TODO wrong place.. not allowed in TD spec? + /* + { + idShort: "contentType", + valueType: "xs:string", + value: "application/json", // TODO + modelType: "Property", + }, + */ + + // 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 securityDefinitionsValues: Array = []; + for (const secKey in td.securityDefinitions) { + const secValue: SecurityScheme = td.securityDefinitions[secKey]; + securityDefinitionsValues.push({ + idShort: secKey, + value: [ + { + idShort: "scheme", + valueType: "xs:string", + value: secValue.scheme, + modelType: "Property", + }, + ], + modelType: "SubmodelElementCollection", + }); + } + values.push({ + idShort: "securityDefinitions", + value: securityDefinitionsValues, + modelType: "SubmodelElementCollection", + }); + + const endpointMetadata: Record = { + idShort: "EndpointMetadata", + // semanticId ? + // embeddedDataSpecifications ? + value: values, + modelType: "SubmodelElementCollection", + }; + + return endpointMetadata; } - /** - * 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); + private createInterfaceMetadata(td: ThingDescription, protocol: string): Record { + const properties: Array = []; + 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 smInformation: SubmodelInformation = { - actions: new Map>(), - events: new Map>(), - properties: new Map>(), - endpointMetadataArray: [], - }; + 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 = []; + + // TODO AID for now supports just *one* href/form + // --> pick the first one that matches protocol (other means in future?) + + // walk over string values like: "href", "contentType", "htv:methodName", ... + for (const formTerm in formElementPicked) { + const formValue = formElementPicked[formTerm]; + if (typeof formValue === "string") { + propertyForm.push({ + idShort: formTerm, + valueType: "xs:string", + value: formValue, + modelType: "Property", + }); + } + } - this.processSubmodel(smInformation, submodel, submodelRegex); + // TODO terms that are not string-based, like op arrays? - return this._transform(smInformation, template); + propertyValues.push({ + idShort: "forms", + value: propertyForm, + modelType: "SubmodelElementCollection", + }); + } + + 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, + description: description, + value: propertyValues, + modelType: "SubmodelElementCollection", + }); + } + } + // Actions + if (td.actions) { + // TODO actions - TBD by AID + } + + // Events + if (td.events) { + // TODO events - TBD by AID + } + } + + const values: Array = []; + // Properties + values.push({ + idShort: "Properties", + value: properties, + modelType: "SubmodelElementCollection", + }); + // Actions + values.push({ + idShort: "Actions", + value: actions, + modelType: "SubmodelElementCollection", + }); + // Events + values.push({ + idShort: "Events", + value: events, + modelType: "SubmodelElementCollection", + }); + + const interfaceMetadata: Record = { + idShort: "InterfaceMetadata", + // semanticId ? + // embeddedDataSpecifications ? + value: values, + modelType: "SubmodelElementCollection", + }; + + return interfaceMetadata; } } + +interface AASInteraction { + endpointMetadata?: Record; + secNamesForEndpoint?: Array; + interaction: Record; +} + +interface SubmodelInformation { + properties: Map>; + actions: Map>; + events: Map>; + + thing: Map>; + + endpointMetadataArray: Array>; +} diff --git a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts index 042e9517c..bbe390ebc 100644 --- a/packages/td-tools/test/AssetInterfaceDescriptionTest.ts +++ b/packages/td-tools/test/AssetInterfaceDescriptionTest.ts @@ -18,196 +18,132 @@ 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 { private assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); - @test 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 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"}` - ); + @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": "bla"}`); 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("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"); - expect(tdObj).to.have.property("security").to.be.an("array").to.have.lengthOf(5); - // Security Modbus + 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"); - // 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 + + // 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") + .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("voltage") + .to.have.property("count") .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.lengthOf(1); + expect(tdObj.properties.count.forms[0]) .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.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("voltage") + .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"); - // 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) + // 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("voltage") + .to.have.property("redDotImage") .to.have.property("forms") .to.be.an("array") - .to.have.lengthOf(2); - } - - @test 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"); + .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"); - 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 + // check count property + expect(tdObj).to.have.property("properties").to.have.property("lastChange"); expect(tdObj) .to.have.property("properties") - .to.have.property("count") + .to.have.property("lastChange") .to.have.property("type") - .that.equals("integer"); - - // form entries + .that.equals("string"); expect(tdObj) .to.have.property("properties") - .to.have.property("count") + .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); - // HTTP - expect(tdObj.properties.count.forms[0]) + expect(tdObj.properties.lastChange.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"); + .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 + // TODO actions and events for counter thing (TBD by AAS) // check RegEx capability with fully qualified submodel const td2 = this.assetInterfaceDescriptionUtil.transformAAS2TD( @@ -228,4 +164,271 @@ class AssetInterfaceDescriptionUtilTest { const td4Obj = JSON.parse(td4); expect(td4Obj).to.not.have.property("properties"); } + + 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: this.td1Base, + properties: { + status: { + type: "string", + observable: true, + descriptions: { + en: "Statistic", + de: "Statistik", + }, + forms: [ + { + href: "stat", + contentType: "application/json", + "htv:methodName": "GET", + op: ["readproperty"], + }, + ], + }, + }, + }; + + @test async "should correctly transform sample TD into JSON submodel"() { + const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["https"]); + + 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); + let hasThingTitle = false; + let hasEndpointMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "title") { + hasThingTitle = true; + expect(smValue).to.have.property("value").to.equal("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); + let hasBase = false; + let hasContentType = false; + let hasSecurity = false; + let hasSecurityDefinitions = false; + for (const endpointMetadataValue of endpointMetadata.value) { + if (endpointMetadataValue.idShort === "base") { + hasBase = true; + expect(endpointMetadataValue.value).to.equal(this.td1Base); + } 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 hasBasicSC = false; + for (const securityDefinitionValue of endpointMetadataValue.value) { + 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 === "scheme") { + hasBasic = true; + expect(sec.value).to.equal("basic"); + } + } + expect(hasBasic).to.equal(true); + } + } + expect(hasBasicSC).to.equal(true); + } + } + expect(hasBase).to.equal(true); + expect(hasContentType).to.equal(false); + expect(hasSecurity).to.equal(true); + expect(hasSecurityDefinitions).to.equal(true); + } + } + expect(hasThingTitle, "No thing title").to.equal(true); + expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true); + + // InterfaceMetadata with properties etc + let hasInterfaceMetadata = false; + for (const smValue of smInterface.value) { + if (smValue.idShort === "InterfaceMetadata") { + hasInterfaceMetadata = true; + expect(smValue).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0); + let hasProperties = false; + for (const interactionValues of smValue.value) { + if (interactionValues.idShort === "Properties") { + hasProperties = true; + expect(interactionValues) + .to.have.property("value") + .to.be.an("array") + .to.have.lengthOf.greaterThan(0); + let hasPropertyStatus = false; + let hasPropertyStatusDescription = 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; + let hasContentType = false; + let hasHtvMethodName = false; + // let hasOp = 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", + ]); + } 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); + expect(hasTitle).to.equal(false); + 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); + } + } + 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 + 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), ["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-cases + } + + @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(); + + const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]); + + const aasObj = JSON.parse(sm); + // 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); + } } 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 zcmbVQcU03`whmR04gsWT2pyy&y?3NY6)p%NKp;R!fIuiBAV?LJP^3vf6hwM6N|#=x z_aahMq)3yZfWC0=n;Guhc{6L?@3+ob>#Vc)clP=AK7V}M)R2S}2p|Sf0QADgEZOxP z(^&uj0FW@~0h|CR6dfpjHi@Eq-QC=gpu9@}!Q8XmzkYlDA_nh7dinc0~e zd^tt}FeTt${N4BrzruBsjgANaI2%+1e26a$ffg4#oBtj3R(-RwTA&vKgn{{@-BCyt zu%ws-7z9H)qg>pPZYp4ZKUYzCu$ro}ISc{yBUqr}?%rq+!3Bv{0mJ?LycNa8(avy~ z7Zfdq@`fP^JFX~SFQ^}3>FXx$4R!W}y1~SyBqU_T3DNum1N>)9kY$iJ?C*#H0RduA z7cY0DC>jn!AjI5JzmxT6g!BA?L6%ToH<+Ia_z%N#)Pg|xNpP~*|J<-AubAVQBSeTb{E={Q4Ed5>~-Ls6! z;-dPZu+yTL@4LtKUP4S8Or!o~o;0rpX*L%E!uIzW@WTS5 z+&~3I9QrBibGKLR!TDq|W`^yEWYZx?*+Y1twye0bMcsVU4-;w~qbsnVIpJ=@#@eK| zMUr;Hz5SW@9A{_>jWkayiciBc@9`>VGZ<^b*iW(Rqb66TaZwpk5SgDl7ez6J3KDLe zW=6~IYm4i)Tb0)9r1JtLzm8OZ&)3RIGsr2G!iMYMDv^J7N#$^W|uXdbRj)=@y?Ul+&;C*OE zU71-}zoUQ)qmyUp=%|G&_U7_;aD{%c&vd3xt8Hw6p5f}?1~iG>6Qw@~uIqD+(L))j zNXYLRH`MUC^~j95BquV559oAla7C`3K&5!=?*?se9V zk&;nh*|_z(T~l|}lNZA*a^W3#Y9)0BJARvQ&}cghSgGh5xLD-7?&nzPH0Os%2NMY8J)CZ_HJD$6o8$+pHIgf zq4)k8xsMDvghZ&%XUlQN4)UUEw=z3O(Krho;nO@Ziv5oWsgB^%(GR_?%Sq!5I23h{4QL%T=Q1uh;u>j z#t*QX+#4%d%wow>Vu;zGUSdy*JMl(Ty?qFMajRd6QQ-3BCAO6v8-)ZaopuGE@Fh#U zx!~i3Y`qgbl36FLzscnfvZ?kroW z%&D{IA%8P`sYPoiFi$BaC6c!yTwj#EXZU4-@9yT8FHq6>e4l&=^}72FsZ0o(FJkUo z(SB)GMO?i$=`UD2rR+g{e4TXZaoEnPTR-(aX}rviG5ms-YxyxH_vITool)1l;|~|V z47hPrylWlfp3GJEpPS%%4~vlD*Y2ohF9M56x5nDP!N|yy(7H+5SvAE+$YLuql0gpvBVB6j`XyvNzS&F!O%DMf^4IJPv|%w2kN^Ni$gXyAteO zoLoOXU7&KTE3_4y1D?%%)f_15qtnpBWYOgz16ngrKe|xr8}n;9gmzayggs+G;&6{M zBM*>NocO#G)fa9`X=$!Svtq;8^-NTQ5g#b21gumKntzS8=WU=!eRt{Z%uY?2`sNXL z4*OG#at3~`NpyyzkZS?ZXz3=rck&=a8PE(NPdrRjM!iSI{wOuKjjOw}O2#>}n&>H?TTUdc$JD zFZ%lS;JR(;vPs3SPgxs^`9lmhqaPnfTrIhDKSlQ=Zwd8Vtf5uqvpxe$?h2p~nR5`c zW0`ApYGyI8*<#babD1?x{)DoR9kwwxZcl|zPly(d%cvU|a-QrUi4qOjzv~%%#hV&( zM?V=9_%WK4Bvy3;5kVBP&m9N6zR9W*RjvvE7Dq4bw8KWPrH)o6V3vYEx~J6tUR6FH zEk3Rsw^@JG3pjN%88bZEh-}DUN}$QPqrWZ@asW;B=u>)XMA^Ll#be~glNvEQ|4qF# z$1w2CW}y|^>90K(RY|a7+s{-bn1K`2M{AecIjMGfhc7Odo*t_6D8Ys{Y7uEs zHwN9VS&HI*QK*WH!5vf&>K{~hM1Go9__g)(HFDd5tL(1MMMpA9Qc;L zKY_oYWc!)ao{q3wEI41SHC^~rNj?O%vn@LAzr(I11nfrX>9hzzh=T`hLFvTgR5wFA zt10fM+nGPp?bUr^rdcUK%KUT+{p!&LF2M+=K@}w_%}O^Zui8@E8adOATB6`D z=m0)stE9&Ty_Yy5Tx&vf(Kc{`)A!Dg=?0|3IA-kFS`xWaAHzw)arkk&zDG)7VEVbK z^`aY#Sl9ez?LqKZP_8a)n&3O+GRuFyc;30l{9j+uR|(5){D9N@!hz8cAU zVJd&Yw1hpri)s1#q@_uh)J)uZ>I<{4#NbzO-mX>K-k$kp9Bv{^>^tY$r+6EMmI+h2 zv9%N@{*xnWTiTL}@^q&$dg zXWczyk?-K@<Mw%ny%80J5a!@HQHN7%j>5$g`41)P8yf2(caM#~uPKtC<#8QJDPft9w@V|LLdV=5y-QtN z!OJI8t5H(GBXdY^!M%q#j(4<&RIdsXTRFMg-xaRZQWeomwIO8ZjvXO8e0ZWCr{4Qy zi^^-kRSvgIMkE7IuGfB*;Zd$&d_X=KM}C5bu+Tl~1J$k8Pmqj+^@gTICQ}^syHXjy z>y&@bq+7)x@$+zLHpqoa*S^DpXUoRMx!9IQdPreKS{G$T;Fd z4_jX32kp7(RS-~+z`j*Cf&E<4KiGFL_&fWhW=pd{7CUK+ZAZ`8poKACt1>pt#5h&5 z9$GVXxb5JVoZD4~DHonN}|UGNm&}GH)%o zRRi+(9!W5GY>v-k-fh2>Mzeft!vD^Z_>Jo(z1f!<6I7buJ|9lH*Uf4KhGPX1R)>b? zo1;;G=1c^=8b3+k%#y$v6M-{3EfmrZhV+A+X-2gDf6_}{iIT`R32L2kFkBR&$Q7Sg zk`rCc=zJv}N=-DJ~bUQ5tO< z;iw!itN8a8j|95bB>E1Gv$|oWnAabcJY+goOEg$$-4;%(fojp_nV$TQWItV?gp&m@ zi~MkQ%w!AYK>0?vD4B1iA2ACohNpkt7t*YX+&}y=yh!XqQX=QkOk`?EL@Y{ZDE#Xd z!#}qX&Wh{2VE)7V|7swd_jV*K&b1THANyC8eBQB!kgng4{Vz4}|5r1gkL<5c?R;dE owEr8~zkYcC4D{#vI}fBn2l!XjXlh7Cer85S__NMfpg&vv54C;MssI20 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": [] -} diff --git a/packages/td-tools/test/util/counterHTTP.aasx b/packages/td-tools/test/util/counterHTTP.aasx new file mode 100644 index 0000000000000000000000000000000000000000..3815efa46d53cea51740d62de78dd5dff9e0e496 GIT binary patch literal 3099 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 literal 0 HcmV?d00001 diff --git a/packages/td-tools/test/util/counterHTTP.json b/packages/td-tools/test/util/counterHTTP.json new file mode 100644 index 000000000..dc31feddd --- /dev/null +++ b/packages/td-tools/test/util/counterHTTP.json @@ -0,0 +1,528 @@ +{ + "assetAdministrationShells": [ + { + "idShort": "SampleAAS", + "id": "https://example.com/ids/aas/7474_9002_6022_1115", + "assetInformation": { + "assetKind": "Type", + "globalAssetId": "https://example.com/ids/asset/3071_4170_8032_4893", + "specificAssetIds": [], + "assetType": "" + }, + "submodels": [ + { + "type": "ModelReference", + "keys": [ + { + "type": "Submodel", + "value": "https://example.com/ids/sm/4333_9041_7022_4184" + } + ] + } + ], + "modelType": "AssetAdministrationShell" + } + ], + "submodels": [ + { + "idShort": "AssetInterfacesDescription", + "description": [ + { + "language": "en", + "text": "Counter example Thing" + }, + { + "language": "de", + "text": "Zähler Beispiel Ding" + }, + { + "language": "it", + "text": "Contatore di esempio" + } + ], + "id": "https://example.com/ids/sm/4333_9041_7022_4184", + "kind": "Instance", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "submodelElements": [ + { + "idShort": "InterfaceHTTP", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "title", + "valueType": "xs:string", + "value": "Counter", + "modelType": "Property" + }, + { + "idShort": "support", + "valueType": "xs:anyURI", + "value": "https://github.com/eclipse-thingweb/node-wot/", + "modelType": "Property" + }, + { + "idShort": "EndpointMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "base", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:anyURI", + "value": "http://plugfest.thingweb.io:8083", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "application/json", + "modelType": "Property" + }, + { + "idShort": "security", + "typeValueListElement": "SubmodelElement", + "value": [ + { + "valueType": "xs:string", + "value": "nosec_sc", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementList" + }, + { + "idShort": "securityDefinitions", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "nosec_sc", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "scheme", + "valueType": "xs:string", + "value": "nosec", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "InterfaceMetadata", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "Properties", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "count", + "description": [ + { + "language": "en", + "text": "Current counter value" + }, + { + "language": "de", + "text": "Derzeitiger Zählerwert" + }, + { + "language": "it", + "text": "Valore attuale del contatore" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "valueType": "xs:string", + "value": "integer", + "modelType": "Property" + }, + { + "idShort": "range", + "valueType": "xs:integer", + "min": "1", + "max": "100", + "modelType": "Range" + }, + { + "idShort": "title", + "valueType": "xs:string", + "value": "Count", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "htv:methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/counter/properties/count", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "countAsImage", + "description": [ + { + "language": "en", + "text": "Current counter value as SVG image" + }, + { + "language": "de", + "text": "Aktueller Zählerwert als SVG-Bild" + }, + { + "language": "it", + "text": "Valore attuale del contatore come immagine SVG" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "htv:methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/counter/properties/countAsImage", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "image/svg+xml", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "redDotImage", + "description": [ + { + "language": "en", + "text": "Red dot image as PNG" + }, + { + "language": "de", + "text": "Rotes Punktbild als PNG" + }, + { + "language": "it", + "text": "Immagine punto rosso come PNG" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "false", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "htv:methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/counter/properties/redDotImage", + "modelType": "Property" + }, + { + "idShort": "contentType", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "image/png;base64", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "lastChange", + "description": [ + { + "language": "en", + "text": "Last change of counter value" + }, + { + "language": "de", + "text": "Letzte Änderung" + }, + { + "language": "it", + "text": "Ultima modifica del valore" + } + ], + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "qualifiers": [], + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "type", + "valueType": "xs:string", + "value": "string", + "modelType": "Property" + }, + { + "idShort": "title", + "valueType": "xs:string", + "value": "Last change", + "modelType": "Property" + }, + { + "idShort": "observable", + "valueType": "xs:boolean", + "value": "true", + "modelType": "Property" + }, + { + "idShort": "forms", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "htv:methodName", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "GET", + "modelType": "Property" + }, + { + "idShort": "href", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "valueType": "xs:string", + "value": "/counter/properties/lastChange", + "modelType": "Property" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Actions", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "Events", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + }, + { + "idShort": "externalDescriptor", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": [ + { + "idShort": "ThingDescription", + "semanticId": { + "type": "ExternalReference", + "keys": [] + }, + "embeddedDataSpecifications": [], + "value": "/aasx/files/counter-http-simple.td.jsonld", + "contentType": "application/json", + "modelType": "File" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "SubmodelElementCollection" + } + ], + "modelType": "Submodel" + } + ], + "conceptDescriptions": [] +}