Skip to content

Commit

Permalink
Merge pull request eclipse-thingweb#1240 from danielpeintner/issue-1238
Browse files Browse the repository at this point in the history
Handle AID terms on affordance level and form level
  • Loading branch information
relu91 authored Feb 21, 2024
2 parents 450ebd9 + 8dd595b commit 6ed05ff
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 27 deletions.
48 changes: 40 additions & 8 deletions packages/td-tools/src/util/asset-interface-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,32 @@ export class AssetInterfaceDescriptionUtil {
return ""; // TODO what is the right value if information cannot be found
}

private getModbusMostSignificantByteFromEndpointMetadata(
endpointMetadata?: Record<string, unknown>
): string | undefined {
if (endpointMetadata?.value instanceof Array) {
for (const v of endpointMetadata.value) {
if (v.idShort === "modv_mostSignificantByte") {
return v.value;
}
}
}
return undefined;
}

private getModbusMostSignificantWordFromEndpointMetadata(
endpointMetadata?: Record<string, unknown>
): string | undefined {
if (endpointMetadata?.value instanceof Array) {
for (const v of endpointMetadata.value) {
if (v.idShort === "modv_mostSignificantWord") {
return v.value;
}
}
}
return undefined;
}

private updateRootMetadata(thing: Thing, endpointMetadata?: Record<string, unknown>) {
const securityDefinitions: {
[k: string]: SecurityScheme;
Expand Down Expand Up @@ -441,6 +467,17 @@ export class AssetInterfaceDescriptionUtil {
contentType: this.getContentTypeFromEndpointMetadata(vi.endpointMetadata),
};

// special treatment for global definitions
// besides contentType there is the AID possibility for mostSignificantByte and mostSignificantWord (Modbus)
const mostSignificantByte = this.getModbusMostSignificantByteFromEndpointMetadata(vi.endpointMetadata);
if (mostSignificantByte != null) {
form["modv:mostSignificantByte"] = mostSignificantByte === "true" || mostSignificantByte === "1";
}
const mostSignificantWord = this.getModbusMostSignificantWordFromEndpointMetadata(vi.endpointMetadata);
if (mostSignificantWord != null) {
form["modv:mostSignificantWord"] = mostSignificantWord === "true" || mostSignificantWord === "1";
}

if (addSecurity) {
// XXX need to add security at form level at all ?
logError("security at form level not added/present");
Expand All @@ -467,15 +504,10 @@ export class AssetInterfaceDescriptionUtil {
if (isAbsoluteUrl(hrefValue)) {
form.href = hrefValue;
} else if (form.href && form.href.length > 0) {
// handle leading/trailing slashes
if (form.href.endsWith("/") && hrefValue.startsWith("/")) {
form.href = form.href + hrefValue.substring(1);
} else if (!form.href.endsWith("/") && !hrefValue.startsWith("/")) {
form.href = form.href + "/" + hrefValue;
} else {
form.href = form.href + hrefValue;
}
form.href = URLToolkit.buildAbsoluteURL(form.href, hrefValue);
} else {
// silently ignore error case
// (no proper base and relative local href)
form.href = hrefValue;
}
}
Expand Down
209 changes: 205 additions & 4 deletions packages/td-tools/test/AssetInterfaceDescriptionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,7 @@ class AssetInterfaceDescriptionUtilTest {
.to.eql("modbus+tcp://192.168.178.146:502/1/40020?quantity=16");
expect(tdObj.properties.device_name.forms[0]).to.have.property("modv:function").to.eql("readHoldingRegisters");
expect(tdObj.properties.device_name.forms[0]).to.have.property("modv:type").to.eql("string");
expect(tdObj.properties.device_name.forms[0])
.to.have.property("contentType")
.to.eql("application/octet-stream");
expect(tdObj.properties.device_name.forms[0]).to.have.property("contentType").to.eql("text/plain");
expect(tdObj.properties.device_name.forms[0]).not.to.have.property("security");

// check property soc
Expand Down Expand Up @@ -400,6 +398,7 @@ class AssetInterfaceDescriptionUtilTest {
let hasContentType = false;
let hasModbusFunction = false;
let hasModbusType = false;
let hasModbusMostSignificantByte = false;
for (const formEntry of propProperty.value) {
if (formEntry.idShort === "href") {
hasHref = true;
Expand All @@ -410,7 +409,9 @@ class AssetInterfaceDescriptionUtilTest {
// expect(formEntry.value).to.equal("readproperty");
} else if (formEntry.idShort === "contentType") {
hasContentType = true;
expect(formEntry.value).to.equal("application/octet-stream");
// Note: It uses the global contentType (locally it was not set in the AID)
expect(formEntry.value).to.equal("text/plain");
// expect(formEntry.value).to.equal("application/octet-stream");
} else if (formEntry.idShort === "modv_function") {
// vs. "modv:function"
hasModbusFunction = true;
Expand All @@ -419,13 +420,18 @@ class AssetInterfaceDescriptionUtilTest {
// vs. "modv:type"
hasModbusType = true;
expect(formEntry.value).to.equal("string");
} else if (formEntry.idShort === "modv_mostSignificantByte") {
// vs. "modv:mostSignificantByte"
hasModbusMostSignificantByte = true;
expect(formEntry.value).to.equal("true");
}
}
expect(hasHref).to.equal(true);
expect(hasOp).to.equal(false);
expect(hasContentType).to.equal(true);
expect(hasModbusFunction).to.equal(true);
expect(hasModbusType).to.equal(true);
expect(hasModbusMostSignificantByte).to.equal(true); // global
}
}
expect(hasType).to.equal(true);
Expand Down Expand Up @@ -1045,6 +1051,201 @@ class AssetInterfaceDescriptionUtilTest {
expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true);
}

td3: ThingDescription = {
"@context": "https://www.w3.org/2022/wot/td/v1.1",
title: "ModbusTD",
securityDefinitions: {
nosec_sc: {
scheme: "nosec",
},
},
security: "nosec_sc",
base: "modbus+tcp://$$addr$$:502/$$unitid$$/",
properties: {
voltage: {
forms: [
{
href: "40001?quantity=2",
contentType: "application/octet-stream",
op: ["readproperty"],
"modv:function": "readHoldingRegisters",
"modv:type": "xsd:float",
"modv:mostSignificantByte": true,
"modv:mostSignificantWord": true,
},
],
},
},
};

@test async "should correctly transform sample TD3 into AID submodel"() {
const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td3));

const smObj = JSON.parse(sm);
// console.log("###\n\n" + JSON.stringify(smObj) + "\n\n###");
const isValid = this.validateAID(smObj);
expect(isValid.valid, isValid.errors).to.equal(true);
expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
expect(smObj).to.have.property("semanticId");
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("idShort").to.equal("InterfaceMODBUS_TCP"); // AID does not allow "+" in idShort, see InterfaceMODBUS+TCP
expect(smInterface).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0);
expect(smInterface)
.to.have.property("semanticId")
.to.be.an("object")
.with.property("keys")
.to.be.an("array")
.to.have.lengthOf.greaterThan(0);
expect(smInterface)
.to.have.property("supplementalSemanticIds")
.to.be.an("array")
.to.have.lengthOf.greaterThan(1); // default WoT-TD and http
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("ModbusTD");
} 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("modbus+tcp://$$addr$$:502/$$unitid$$/");
} 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]).to.have.property("value");
const modelReferenceValue = endpointMetadataValue.value[0].value;
expect(modelReferenceValue).to.have.property("type").to.equal("ModelReference");
expect(modelReferenceValue).to.have.property("keys").to.be.an("array").to.have.lengthOf(5);
expect(modelReferenceValue.keys[4]).to.have.property("value").to.equal("nosec_sc");
} else if (endpointMetadataValue.idShort === "securityDefinitions") {
hasSecurityDefinitions = true;
}
}
expect(hasBase).to.equal(true); // AID requires base to exist
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);

// InteractionMetadata with properties etc
let hasInteractionMetadata = false;
for (const smValue of smInterface.value) {
if (smValue.idShort === "InteractionMetadata") {
hasInteractionMetadata = 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 hasPropertyVoltage = false;
for (const propertyValue of interactionValues.value) {
if (propertyValue.idShort === "voltage") {
hasPropertyVoltage = 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;
} else if (propProperty.idShort === "title") {
hasTitle = true;
} else if (propProperty.idShort === "observable") {
hasObservable = 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 hasOp = false;
let hasModbusFunction = false;
let hasModbusType = false;
let hasModbusMostSignificantByte = false;
let hasModbusMostSignificantWord = false;
for (const formEntry of propProperty.value) {
if (formEntry.idShort === "href") {
hasHref = true;
expect(formEntry.value).to.be.oneOf([
"40001?quantity=2",
"modbus+tcp://$$addr$$:502/$$unitid$$/40001?quantity=2",
]); // absolute or relative
} else if (formEntry.idShort === "contentType") {
hasContentType = true;
expect(formEntry.value).to.equal("application/octet-stream");
} else if (formEntry.idShort === "op") {
hasOp = true;
// Note: AID does not know "op"
// expect(formEntry.value).to.equal("readproperty");
} else if (formEntry.idShort === "modv_function") {
hasModbusFunction = true;
expect(formEntry.value).to.equal("readHoldingRegisters");
} else if (formEntry.idShort === "modv_type") {
hasModbusType = true;
expect(formEntry.value).to.equal("xsd:float");
expect(formEntry.valueType).to.equal("xs:string");
} else if (formEntry.idShort === "modv_mostSignificantByte") {
hasModbusMostSignificantByte = true;
expect(formEntry.value).to.equal("true");
expect(formEntry.valueType).to.equal("xs:boolean");
} else if (formEntry.idShort === "modv_mostSignificantWord") {
hasModbusMostSignificantWord = true;
expect(formEntry.value).to.equal("true");
expect(formEntry.valueType).to.equal("xs:boolean");
}
}
expect(hasHref).to.equal(true);
expect(hasContentType).to.equal(true);
expect(hasOp).to.equal(false);
expect(hasModbusFunction).to.equal(true);
expect(hasModbusType).to.equal(true);
expect(hasModbusMostSignificantByte).to.equal(true);
expect(hasModbusMostSignificantWord).to.equal(true);
}
}
expect(hasType).to.equal(false);
expect(hasTitle).to.equal(false);
expect(hasObservable).to.equal(false);
expect(hasForms).to.equal(true);
}
}
expect(hasPropertyVoltage).to.equal(true);
}
}
expect(hasProperties).to.equal(true);
}
}
expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true);
}

@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");
Expand Down
10 changes: 1 addition & 9 deletions packages/td-tools/test/util/AIDSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1942,15 +1942,7 @@
"enum": ["xs:string"]
},
"value": {
"type": "string",
"enum": [
"application/json",
"application/octet-stream",
"application/pdf",
"application/rdf+xml",
"image/svg+xml",
"image/png;base64"
]
"type": "string"
},
"valueId": {
"$ref": "#/definitions/Reference"
Expand Down
Loading

0 comments on commit 6ed05ff

Please sign in to comment.