@node-wot/td-tools
Advanced tools
Comparing version 0.8.7 to 0.8.8
export declare class AssetInterfaceDescriptionUtil { | ||
transformToTD(aid: string, template?: string, submodelRegex?: string): string; | ||
transformAAS2TD(aas: string, template?: string, submodelRegex?: string): string; | ||
transformSM2TD(aid: string, template?: string, submodelRegex?: string): string; | ||
transformTD2AAS(td: string, protocols?: string[]): string; | ||
transformTD2SM(tdAsString: string, protocols?: string[]): string; | ||
private getProtocolPrefixes; | ||
private updateProtocolPrefixes; | ||
private getBaseFromEndpointMetadata; | ||
private getContentTypeFromEndpointMetadata; | ||
private getSecuritySchemesFromEndpointMetadata; | ||
private getSecurityDefinitionsFromEndpointMetadata; | ||
private getSecurityFromEndpointMetadata; | ||
private createInteractionForm; | ||
@@ -10,5 +18,4 @@ private processSubmodel; | ||
private _transform; | ||
transformToTD(aid: string, template?: string, submodelRegex?: string): string; | ||
transformAAS2TD(aas: string, template?: string, submodelRegex?: string): string; | ||
transformSM2TD(aid: string, template?: string, submodelRegex?: string): string; | ||
private createEndpointMetadata; | ||
private createInterfaceMetadata; | ||
} |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -7,2 +30,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
exports.AssetInterfaceDescriptionUtil = void 0; | ||
const TDParser = __importStar(require("../td-parser")); | ||
const debug_1 = __importDefault(require("debug")); | ||
@@ -12,5 +36,134 @@ const namespace = "node-wot:td-tools:asset-interface-description-util"; | ||
const logInfo = (0, debug_1.default)(`${namespace}:info`); | ||
const noSecSS = { scheme: "nosec" }; | ||
const noSecName = 0 + "_sc"; | ||
const logError = (0, debug_1.default)(`${namespace}:error`); | ||
class AssetInterfaceDescriptionUtil { | ||
transformToTD(aid, template, submodelRegex) { | ||
return this.transformAAS2TD(aid, template, submodelRegex); | ||
} | ||
transformAAS2TD(aas, template, submodelRegex) { | ||
const smInformation = this.getSubmodelInformation(aas, submodelRegex); | ||
return this._transform(smInformation, template); | ||
} | ||
transformSM2TD(aid, template, submodelRegex) { | ||
const submodel = JSON.parse(aid); | ||
const smInformation = { | ||
actions: new Map(), | ||
events: new Map(), | ||
properties: new Map(), | ||
endpointMetadataArray: [], | ||
thing: new Map(), | ||
}; | ||
this.processSubmodel(smInformation, submodel, submodelRegex); | ||
return this._transform(smInformation, template); | ||
} | ||
transformTD2AAS(td, protocols) { | ||
const submodel = this.transformTD2SM(td, protocols); | ||
const submodelObj = JSON.parse(submodel); | ||
const submodelId = submodelObj.id; | ||
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); | ||
} | ||
transformTD2SM(tdAsString, protocols) { | ||
const td = TDParser.parseTD(tdAsString); | ||
const aidID = td.id ? td.id : "ID" + Math.random(); | ||
logInfo("TD " + td.title + " parsed..."); | ||
if (protocols === undefined || protocols.length === 0) { | ||
protocols = this.getProtocolPrefixes(td); | ||
} | ||
const submdelElements = []; | ||
for (const protocol of protocols) { | ||
const submodelElementIdShort = protocol === undefined ? "Interface" : "Interface" + protocol.toUpperCase(); | ||
const submdelElement = { | ||
idShort: submodelElementIdShort, | ||
value: [ | ||
{ | ||
idShort: "title", | ||
valueType: "xs:string", | ||
value: td.title, | ||
modelType: "Property", | ||
}, | ||
this.createEndpointMetadata(td), | ||
this.createInterfaceMetadata(td, protocol), | ||
], | ||
modelType: "SubmodelElementCollection", | ||
}; | ||
submdelElements.push(submdelElement); | ||
} | ||
const aidObject = { | ||
idShort: "AssetInterfacesDescription", | ||
id: aidID, | ||
kind: "Instance", | ||
description: [ | ||
{ | ||
language: "en", | ||
text: td.title, | ||
}, | ||
], | ||
submodelElements: submdelElements, | ||
modelType: "Submodel", | ||
}; | ||
return JSON.stringify(aidObject); | ||
} | ||
getProtocolPrefixes(td) { | ||
const protocols = []; | ||
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; | ||
} | ||
updateProtocolPrefixes(forms, protocols) { | ||
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); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
getBaseFromEndpointMetadata(endpointMetadata) { | ||
@@ -36,15 +189,18 @@ if ((endpointMetadata === null || endpointMetadata === void 0 ? void 0 : endpointMetadata.value) && endpointMetadata.value instanceof Array) { | ||
} | ||
getSecuritySchemesFromEndpointMetadata(endpointMetadata) { | ||
getSecurityDefinitionsFromEndpointMetadata(endpointMetadata) { | ||
const securityDefinitions = {}; | ||
if ((endpointMetadata === null || endpointMetadata === void 0 ? void 0 : endpointMetadata.value) && endpointMetadata.value instanceof Array) { | ||
for (const v of endpointMetadata.value) { | ||
if (v.idShort === "securityDefinitions") { | ||
const securitySchemes = []; | ||
if (v.value && v.value instanceof Array) { | ||
for (const secValue of v.value) { | ||
const ss = { 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) { | ||
if (securityDefinitionsValues.value instanceof Array) { | ||
for (const securityDefinitionsValue of securityDefinitionsValues.value) { | ||
if (securityDefinitionsValue.idShort === "scheme") { | ||
if (securityDefinitionsValue.value) { | ||
const ss = { scheme: securityDefinitionsValue.value }; | ||
securityDefinitions[securityDefinitionsValues.idShort] = ss; | ||
} | ||
} | ||
} | ||
@@ -55,8 +211,24 @@ } | ||
} | ||
return securitySchemes; | ||
} | ||
} | ||
} | ||
return undefined; | ||
return securityDefinitions; | ||
} | ||
getSecurityFromEndpointMetadata(endpointMetadata) { | ||
const security = []; | ||
if ((endpointMetadata === null || endpointMetadata === void 0 ? void 0 : 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; | ||
} | ||
createInteractionForm(vi, addSecurity) { | ||
@@ -68,53 +240,51 @@ const form = { | ||
if (addSecurity) { | ||
const securitySchemes = this.getSecuritySchemesFromEndpointMetadata(vi.endpointMetadata); | ||
if (securitySchemes === undefined) { | ||
form.security = [noSecName]; | ||
} | ||
else { | ||
if (vi.secNamesForEndpoint) { | ||
form.security = vi.secNamesForEndpoint; | ||
} | ||
} | ||
logError("security at form level not added/present"); | ||
} | ||
if (vi.interaction.value instanceof Array) { | ||
for (const v of vi.interaction.value) { | ||
if (v.idShort === "href") { | ||
if (form.href && form.href.length > 0) { | ||
form.href = form.href + v.value; | ||
} | ||
else { | ||
form.href = v.value; | ||
} | ||
} | ||
else if (typeof v.idShort === "string" && v.idShort.length > 0) { | ||
if (v.value) { | ||
form[v.idShort] = v.value; | ||
if (v.valueType && | ||
v.valueType && | ||
v.valueType.dataObjectType && | ||
v.valueType.dataObjectType.name && | ||
typeof v.valueType.dataObjectType.name === "string") { | ||
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; | ||
for (const iv of vi.interaction.value) { | ||
if (iv.idShort === "forms") { | ||
if (iv.value instanceof Array) { | ||
for (const v of iv.value) { | ||
if (v.idShort === "href") { | ||
if (form.href && form.href.length > 0) { | ||
form.href = form.href + v.value; | ||
} | ||
else { | ||
form.href = v.value; | ||
} | ||
} | ||
else if (typeof v.idShort === "string" && v.idShort.length > 0) { | ||
if (v.value) { | ||
form[v.idShort] = v.value; | ||
if (v.valueType && | ||
v.valueType && | ||
v.valueType.dataObjectType && | ||
v.valueType.dataObjectType.name && | ||
typeof v.valueType.dataObjectType.name === "string") { | ||
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; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
@@ -128,3 +298,3 @@ } | ||
processSubmodel(smInformation, submodel, submodelRegex) { | ||
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) { | ||
@@ -158,2 +328,9 @@ for (const submodelElement of submodel.submodelElements) { | ||
} | ||
else if (smValue.idShort === "InterfaceMetadata") { | ||
} | ||
else if (smValue.idShort === "externalDescriptor") { | ||
} | ||
else { | ||
smInformation.thing.set(smValue.idShort, smValue.value); | ||
} | ||
} | ||
@@ -226,2 +403,3 @@ } | ||
endpointMetadataArray: [], | ||
thing: new Map(), | ||
}; | ||
@@ -239,2 +417,9 @@ if (aidModel instanceof Object && aidModel.submodels) { | ||
const thing = template ? JSON.parse(template) : {}; | ||
for (const [key, value] of smInformation.thing) { | ||
if (typeof value === "string") { | ||
thing[key] = value; | ||
} | ||
else { | ||
} | ||
} | ||
if (!thing["@context"]) { | ||
@@ -249,37 +434,16 @@ thing["@context"] = "https://www.w3.org/2022/wot/td/v1.1"; | ||
} | ||
let cnt = 1; | ||
const secSchemeNamesAll = new Array(); | ||
const secNamesForEndpointMetadata = new Map(); | ||
for (const endpointMetadata of smInformation.endpointMetadataArray) { | ||
const secNames = []; | ||
const securitySchemes = this.getSecuritySchemesFromEndpointMetadata(endpointMetadata); | ||
if (securitySchemes === undefined) { | ||
thing.securityDefinitions[noSecName] = noSecSS; | ||
secSchemeNamesAll.push(noSecName); | ||
secNames.push(noSecName); | ||
thing.securityDefinitions = this.getSecurityDefinitionsFromEndpointMetadata(endpointMetadata); | ||
thing.security = this.getSecurityFromEndpointMetadata(endpointMetadata); | ||
for (const [key, value] of Object.entries(thing.securityDefinitions)) { | ||
secNames.push(key); | ||
} | ||
else { | ||
for (const secScheme of securitySchemes) { | ||
const secName = cnt + "_sc"; | ||
thing.securityDefinitions[secName] = secScheme; | ||
secSchemeNamesAll.push(secName); | ||
secNames.push(secName); | ||
cnt++; | ||
} | ||
} | ||
secNamesForEndpointMetadata.set(endpointMetadata, secNames); | ||
} | ||
if (secSchemeNamesAll.length === 0) { | ||
thing.securityDefinitions.nosec_sc = noSecSS; | ||
thing.security = [noSecName]; | ||
} | ||
else { | ||
thing.security = secSchemeNamesAll; | ||
} | ||
logDebug("########### PROPERTIES (" + smInformation.properties.size + ")"); | ||
if (smInformation.properties.size > 0) { | ||
thing.properties = {}; | ||
for (const entry of smInformation.properties.entries()) { | ||
const key = entry[0]; | ||
const value = entry[1]; | ||
for (const [key, value] of smInformation.properties.entries()) { | ||
logInfo("Property" + key + " = " + value); | ||
@@ -289,12 +453,53 @@ thing.properties[key] = {}; | ||
for (const vi of value) { | ||
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"; | ||
for (const keyInteraction in vi.interaction) { | ||
if (keyInteraction === "description") { | ||
const aasDescription = vi.interaction[keyInteraction]; | ||
const tdDescription = {}; | ||
if (aasDescription instanceof Array) { | ||
for (const aasDescriptionEntry of aasDescription) { | ||
if (aasDescriptionEntry.language && aasDescriptionEntry.text) { | ||
const language = aasDescriptionEntry.language; | ||
const text = aasDescriptionEntry.text; | ||
tdDescription[language] = text; | ||
} | ||
} | ||
else { | ||
thing.properties[key].type = constraint.value; | ||
} | ||
} | ||
thing.properties[key].descriptions = tdDescription; | ||
} | ||
else if (keyInteraction === "value") { | ||
if (vi.interaction.value instanceof Array) { | ||
for (const interactionValue of vi.interaction.value) | ||
if (interactionValue.idShort === "type") { | ||
if (interactionValue.value === "float") { | ||
thing.properties[key].type = "number"; | ||
} | ||
else { | ||
thing.properties[key].type = interactionValue.value; | ||
} | ||
} | ||
else if (interactionValue.idShort === "range") { | ||
if (interactionValue.min) { | ||
thing.properties[key].min = interactionValue.min; | ||
} | ||
if (interactionValue.max) { | ||
thing.properties[key].max = interactionValue.max; | ||
} | ||
} | ||
else if (interactionValue.idShort === "observable") { | ||
thing.properties[key].observable = interactionValue.value === "true"; | ||
} | ||
else if (interactionValue.idShort === "readOnly") { | ||
thing.properties[key].readOnly = interactionValue.value === "true"; | ||
} | ||
else if (interactionValue.idShort === "writeOnly") { | ||
thing.properties[key].writeOnly = interactionValue.value === "true"; | ||
} | ||
else if (interactionValue.idShort === "forms") { | ||
} | ||
else { | ||
const key2 = interactionValue.idShort; | ||
thing.properties[key][key2] = interactionValue.value; | ||
} | ||
} | ||
} | ||
} | ||
@@ -312,5 +517,3 @@ if (vi.endpointMetadata) { | ||
thing.actions = {}; | ||
for (const entry of smInformation.actions.entries()) { | ||
const key = entry[0]; | ||
const value = entry[1]; | ||
for (const [key, value] of smInformation.actions.entries()) { | ||
logInfo("Action" + key + " = " + value); | ||
@@ -331,5 +534,3 @@ thing.actions[key] = {}; | ||
thing.events = {}; | ||
for (const entry of smInformation.events.entries()) { | ||
const key = entry[0]; | ||
const value = entry[1]; | ||
for (const [key, value] of smInformation.events.entries()) { | ||
logInfo("Event " + key + " = " + value); | ||
@@ -349,19 +550,173 @@ thing.events[key] = {}; | ||
} | ||
transformToTD(aid, template, submodelRegex) { | ||
return this.transformAAS2TD(aid, submodelRegex); | ||
createEndpointMetadata(td) { | ||
const values = []; | ||
if (td.base) { | ||
values.push({ | ||
idShort: "base", | ||
valueType: "xs:anyURI", | ||
value: td.base, | ||
modelType: "Property", | ||
}); | ||
} | ||
const securityValues = []; | ||
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", | ||
}); | ||
const securityDefinitionsValues = []; | ||
for (const secKey in td.securityDefinitions) { | ||
const secValue = 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 = { | ||
idShort: "EndpointMetadata", | ||
value: values, | ||
modelType: "SubmodelElementCollection", | ||
}; | ||
return endpointMetadata; | ||
} | ||
transformAAS2TD(aas, template, submodelRegex) { | ||
const smInformation = this.getSubmodelInformation(aas, submodelRegex); | ||
return this._transform(smInformation, template); | ||
} | ||
transformSM2TD(aid, template, submodelRegex) { | ||
const submodel = JSON.parse(aid); | ||
const smInformation = { | ||
actions: new Map(), | ||
events: new Map(), | ||
properties: new Map(), | ||
endpointMetadataArray: [], | ||
createInterfaceMetadata(td, protocol) { | ||
var _a; | ||
const properties = []; | ||
const actions = []; | ||
const events = []; | ||
if (protocol) { | ||
if (td.properties) { | ||
for (const propertyKey in td.properties) { | ||
const propertyValue = td.properties[propertyKey]; | ||
let formElementPicked; | ||
if (propertyValue.forms) { | ||
for (const formElementProperty of propertyValue.forms) { | ||
if ((_a = formElementProperty.href) === null || _a === void 0 ? void 0 : _a.startsWith(protocol)) { | ||
formElementPicked = formElementProperty; | ||
break; | ||
} | ||
} | ||
} | ||
if (formElementPicked === undefined) { | ||
continue; | ||
} | ||
const propertyValues = []; | ||
if (propertyValue.type) { | ||
propertyValues.push({ | ||
idShort: "type", | ||
valueType: "xs:string", | ||
value: propertyValue.type, | ||
modelType: "Property", | ||
}); | ||
} | ||
if (propertyValue.title) { | ||
propertyValues.push({ | ||
idShort: "title", | ||
valueType: "xs:string", | ||
value: propertyValue.title, | ||
modelType: "Property", | ||
}); | ||
} | ||
if (propertyValue.observable) { | ||
propertyValues.push({ | ||
idShort: "observable", | ||
valueType: "xs:boolean", | ||
value: `${propertyValue.observable}`, | ||
modelType: "Property", | ||
}); | ||
} | ||
if (formElementPicked) { | ||
const propertyForm = []; | ||
for (const formTerm in formElementPicked) { | ||
const formValue = formElementPicked[formTerm]; | ||
if (typeof formValue === "string") { | ||
propertyForm.push({ | ||
idShort: formTerm, | ||
valueType: "xs:string", | ||
value: formValue, | ||
modelType: "Property", | ||
}); | ||
} | ||
} | ||
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) { | ||
description = []; | ||
description.push({ | ||
language: "en", | ||
text: propertyValue.description, | ||
}); | ||
} | ||
properties.push({ | ||
idShort: propertyKey, | ||
description: description, | ||
value: propertyValues, | ||
modelType: "SubmodelElementCollection", | ||
}); | ||
} | ||
} | ||
if (td.actions) { | ||
} | ||
if (td.events) { | ||
} | ||
} | ||
const values = []; | ||
values.push({ | ||
idShort: "Properties", | ||
value: properties, | ||
modelType: "SubmodelElementCollection", | ||
}); | ||
values.push({ | ||
idShort: "Actions", | ||
value: actions, | ||
modelType: "SubmodelElementCollection", | ||
}); | ||
values.push({ | ||
idShort: "Events", | ||
value: events, | ||
modelType: "SubmodelElementCollection", | ||
}); | ||
const interfaceMetadata = { | ||
idShort: "InterfaceMetadata", | ||
value: values, | ||
modelType: "SubmodelElementCollection", | ||
}; | ||
this.processSubmodel(smInformation, submodel, submodelRegex); | ||
return this._transform(smInformation, template); | ||
return interfaceMetadata; | ||
} | ||
@@ -368,0 +723,0 @@ } |
{ | ||
"name": "@node-wot/td-tools", | ||
"version": "0.8.7", | ||
"version": "0.8.8", | ||
"description": "W3C Web of Things (WoT) Thing Description parser, serializer, and other tools", | ||
@@ -5,0 +5,0 @@ "author": "Eclipse Thingweb <thingweb-dev@eclipse.org> (https://thingweb.io/)", |
# TD (Thing Description) tools of node-wot | ||
Current Maintainer(s): [@danielpeintner](https://github.com/danielpeintner) [@relu91](https://github.com/relu91) | ||
## Getting Started | ||
@@ -4,0 +6,0 @@ |
@@ -19,42 +19,219 @@ /******************************************************************************** | ||
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<string, unknown>; | ||
secNamesForEndpoint?: Array<string>; | ||
interaction: Record<string, unknown>; | ||
} | ||
/** | ||
* 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<string, Array<AASInteraction>>; | ||
actions: Map<string, Array<AASInteraction>>; | ||
events: Map<string, Array<AASInteraction>>; | ||
/** | ||
* 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<Record<string, unknown>>; | ||
} | ||
const smInformation: SubmodelInformation = { | ||
actions: new Map<string, Array<AASInteraction>>(), | ||
events: new Map<string, Array<AASInteraction>>(), | ||
properties: new Map<string, Array<AASInteraction>>(), | ||
endpointMetadataArray: [], | ||
thing: new Map<string, Record<string, unknown>>(), | ||
}; | ||
const noSecSS: SecurityScheme = { scheme: "nosec" }; | ||
const noSecName = 0 + "_sc"; | ||
this.processSubmodel(smInformation, submodel, submodelRegex); | ||
export class AssetInterfaceDescriptionUtil { | ||
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); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
private getBaseFromEndpointMetadata(endpointMetadata?: Record<string, unknown>): string { | ||
@@ -84,22 +261,25 @@ if (endpointMetadata?.value && endpointMetadata.value instanceof Array) { | ||
private getSecuritySchemesFromEndpointMetadata( | ||
endpointMetadata?: Record<string, unknown> | ||
): Array<SecurityScheme> | undefined { | ||
private getSecurityDefinitionsFromEndpointMetadata(endpointMetadata?: Record<string, unknown>): { | ||
[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<SecurityScheme> = []; | ||
// const securitySchemes: Array<SecurityScheme> = []; | ||
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; | ||
} | ||
} | ||
} | ||
@@ -110,9 +290,29 @@ } | ||
} | ||
return securitySchemes; | ||
} | ||
} | ||
} | ||
return undefined; | ||
return securityDefinitions; | ||
} | ||
private getSecurityFromEndpointMetadata( | ||
endpointMetadata?: Record<string, unknown> | ||
): 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 { | ||
@@ -123,7 +323,10 @@ const form: TD.Form = { | ||
}; | ||
// 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 { | ||
@@ -134,50 +337,58 @@ if (vi.secNamesForEndpoint) { | ||
} | ||
*/ | ||
} | ||
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 ? | ||
} | ||
} | ||
} | ||
} | ||
@@ -197,3 +408,3 @@ } | ||
): 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) { | ||
@@ -230,2 +441,8 @@ for (const submodelElement of submodel.submodelElements) { | ||
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); | ||
} | ||
@@ -300,2 +517,3 @@ } | ||
endpointMetadataArray: [], | ||
thing: new Map<string, Record<string, unknown>>(), | ||
}; | ||
@@ -317,3 +535,12 @@ | ||
// 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"]) { | ||
@@ -328,35 +555,20 @@ thing["@context"] = "https://www.w3.org/2022/wot/td/v1.1"; | ||
// 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<string>(); | ||
// let cnt = 1; | ||
const secNamesForEndpointMetadata = new Map<Record<string, unknown>, string[]>(); | ||
for (const endpointMetadata of smInformation.endpointMetadataArray) { | ||
const secNames: Array<string> = []; | ||
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[]]; | ||
} | ||
@@ -369,5 +581,3 @@ // add interactions | ||
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); | ||
@@ -379,12 +589,61 @@ | ||
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<string, string> = {}; | ||
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; | ||
} | ||
} | ||
} | ||
} | ||
@@ -406,5 +665,3 @@ | ||
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); | ||
@@ -430,5 +687,3 @@ | ||
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); | ||
@@ -452,42 +707,247 @@ | ||
/** @deprecated use transformAAS2TD method instead */ | ||
public transformToTD(aid: string, template?: string, submodelRegex?: string): string { | ||
return this.transformAAS2TD(aid, submodelRegex); | ||
} | ||
private createEndpointMetadata(td: ThingDescription): Record<string, unknown> { | ||
const values: Array<unknown> = []; | ||
/** | ||
* 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); | ||
// base ? | ||
if (td.base) { | ||
values.push({ | ||
idShort: "base", | ||
valueType: "xs:anyURI", | ||
value: td.base, // TODO | ||
modelType: "Property", | ||
}); | ||
} | ||
// TODO wrong place.. not allowed in TD spec? | ||
/* | ||
{ | ||
idShort: "contentType", | ||
valueType: "xs:string", | ||
value: "application/json", // TODO | ||
modelType: "Property", | ||
}, | ||
*/ | ||
// security | ||
const securityValues: Array<unknown> = []; | ||
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<unknown> = []; | ||
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<string, unknown> = { | ||
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<string, unknown> { | ||
const properties: Array<unknown> = []; | ||
const actions: Array<unknown> = []; | ||
const events: Array<unknown> = []; | ||
const smInformation: SubmodelInformation = { | ||
actions: new Map<string, Array<AASInteraction>>(), | ||
events: new Map<string, Array<AASInteraction>>(), | ||
properties: new Map<string, Array<AASInteraction>>(), | ||
endpointMetadataArray: [], | ||
if (protocol) { | ||
// Properties | ||
if (td.properties) { | ||
for (const propertyKey in td.properties) { | ||
const propertyValue: PropertyElement = td.properties[propertyKey]; | ||
// check whether protocol prefix exists for a form | ||
let formElementPicked: FormElementBase | undefined; | ||
if (propertyValue.forms) { | ||
for (const formElementProperty of propertyValue.forms) { | ||
if (formElementProperty.href?.startsWith(protocol)) { | ||
formElementPicked = formElementProperty; | ||
// found matching form --> abort loop | ||
break; | ||
} | ||
} | ||
} | ||
if (formElementPicked === undefined) { | ||
// do not add this property, since there will be no href of interest | ||
continue; | ||
} | ||
const propertyValues: Array<unknown> = []; | ||
// 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<unknown> = []; | ||
// 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", | ||
}); | ||
} | ||
} | ||
// TODO terms that are not string-based, like op arrays? | ||
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<unknown> = []; | ||
// 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<string, unknown> = { | ||
idShort: "InterfaceMetadata", | ||
// semanticId ? | ||
// embeddedDataSpecifications ? | ||
value: values, | ||
modelType: "SubmodelElementCollection", | ||
}; | ||
this.processSubmodel(smInformation, submodel, submodelRegex); | ||
return this._transform(smInformation, template); | ||
return interfaceMetadata; | ||
} | ||
} | ||
interface AASInteraction { | ||
endpointMetadata?: Record<string, unknown>; | ||
secNamesForEndpoint?: Array<string>; | ||
interaction: Record<string, unknown>; | ||
} | ||
interface SubmodelInformation { | ||
properties: Map<string, Array<AASInteraction>>; | ||
actions: Map<string, Array<AASInteraction>>; | ||
events: Map<string, Array<AASInteraction>>; | ||
thing: Map<string, Record<string, unknown>>; | ||
endpointMetadataArray: Array<Record<string, unknown>>; | ||
} |
@@ -7,6 +7,4 @@ # Thing Description Utilities | ||
### Sample Application | ||
### Sample Applications | ||
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. | ||
#### Prerequisites | ||
@@ -18,8 +16,10 @@ | ||
#### 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 | ||
@@ -41,12 +41,12 @@ | ||
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") | ||
@@ -69,8 +69,37 @@ // do work as usual | ||
#### Run the sample script | ||
#### WoT TD to AAS/AID | ||
`node aid-client.js` | ||
The example tries to load online counter TD and converts it to AAS JSON format. | ||
It will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count | ||
```js | ||
// td-to-aid.js | ||
AssetInterfaceDescriptionUtil = require("@node-wot/td-tools").AssetInterfaceDescriptionUtil; | ||
Note: make sure that the file `AID_v03_counter.json` is in the same folder as the script. | ||
let assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil(); | ||
async function example() { | ||
try { | ||
const response = await fetch("http://plugfest.thingweb.io:8083/counter"); | ||
const counterTD = await response.json(); | ||
const sm = assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]); | ||
// print JSON format of AAS | ||
console.log(sm); | ||
} catch (err) { | ||
console.log(err); | ||
} | ||
} | ||
// launch example | ||
example(); | ||
``` | ||
#### Run the sample scripts | ||
`node aid-to-td.js` | ||
... will show the counter value retrieved from http://plugfest.thingweb.io:8083/counter/properties/count | ||
Note: make sure that the file `counterHTTP.json` is in the same folder as the script. | ||
`node td-to-aid.js` | ||
... will show the online counter im AAS/AID JSON format (usable by AASX Package Explorer 2023-08-01.alpha). |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
232077
3936
55