Skip to content

Commit 6ed05ff

Browse files
authored
Merge pull request #1240 from danielpeintner/issue-1238
Handle AID terms on affordance level and form level
2 parents 450ebd9 + 8dd595b commit 6ed05ff

4 files changed

Lines changed: 258 additions & 27 deletions

File tree

packages/td-tools/src/util/asset-interface-description.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,32 @@ export class AssetInterfaceDescriptionUtil {
383383
return ""; // TODO what is the right value if information cannot be found
384384
}
385385

386+
private getModbusMostSignificantByteFromEndpointMetadata(
387+
endpointMetadata?: Record<string, unknown>
388+
): string | undefined {
389+
if (endpointMetadata?.value instanceof Array) {
390+
for (const v of endpointMetadata.value) {
391+
if (v.idShort === "modv_mostSignificantByte") {
392+
return v.value;
393+
}
394+
}
395+
}
396+
return undefined;
397+
}
398+
399+
private getModbusMostSignificantWordFromEndpointMetadata(
400+
endpointMetadata?: Record<string, unknown>
401+
): string | undefined {
402+
if (endpointMetadata?.value instanceof Array) {
403+
for (const v of endpointMetadata.value) {
404+
if (v.idShort === "modv_mostSignificantWord") {
405+
return v.value;
406+
}
407+
}
408+
}
409+
return undefined;
410+
}
411+
386412
private updateRootMetadata(thing: Thing, endpointMetadata?: Record<string, unknown>) {
387413
const securityDefinitions: {
388414
[k: string]: SecurityScheme;
@@ -441,6 +467,17 @@ export class AssetInterfaceDescriptionUtil {
441467
contentType: this.getContentTypeFromEndpointMetadata(vi.endpointMetadata),
442468
};
443469

470+
// special treatment for global definitions
471+
// besides contentType there is the AID possibility for mostSignificantByte and mostSignificantWord (Modbus)
472+
const mostSignificantByte = this.getModbusMostSignificantByteFromEndpointMetadata(vi.endpointMetadata);
473+
if (mostSignificantByte != null) {
474+
form["modv:mostSignificantByte"] = mostSignificantByte === "true" || mostSignificantByte === "1";
475+
}
476+
const mostSignificantWord = this.getModbusMostSignificantWordFromEndpointMetadata(vi.endpointMetadata);
477+
if (mostSignificantWord != null) {
478+
form["modv:mostSignificantWord"] = mostSignificantWord === "true" || mostSignificantWord === "1";
479+
}
480+
444481
if (addSecurity) {
445482
// XXX need to add security at form level at all ?
446483
logError("security at form level not added/present");
@@ -467,15 +504,10 @@ export class AssetInterfaceDescriptionUtil {
467504
if (isAbsoluteUrl(hrefValue)) {
468505
form.href = hrefValue;
469506
} else if (form.href && form.href.length > 0) {
470-
// handle leading/trailing slashes
471-
if (form.href.endsWith("/") && hrefValue.startsWith("/")) {
472-
form.href = form.href + hrefValue.substring(1);
473-
} else if (!form.href.endsWith("/") && !hrefValue.startsWith("/")) {
474-
form.href = form.href + "/" + hrefValue;
475-
} else {
476-
form.href = form.href + hrefValue;
477-
}
507+
form.href = URLToolkit.buildAbsoluteURL(form.href, hrefValue);
478508
} else {
509+
// silently ignore error case
510+
// (no proper base and relative local href)
479511
form.href = hrefValue;
480512
}
481513
}

packages/td-tools/test/AssetInterfaceDescriptionTest.ts

Lines changed: 205 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,7 @@ class AssetInterfaceDescriptionUtilTest {
238238
.to.eql("modbus+tcp://192.168.178.146:502/1/40020?quantity=16");
239239
expect(tdObj.properties.device_name.forms[0]).to.have.property("modv:function").to.eql("readHoldingRegisters");
240240
expect(tdObj.properties.device_name.forms[0]).to.have.property("modv:type").to.eql("string");
241-
expect(tdObj.properties.device_name.forms[0])
242-
.to.have.property("contentType")
243-
.to.eql("application/octet-stream");
241+
expect(tdObj.properties.device_name.forms[0]).to.have.property("contentType").to.eql("text/plain");
244242
expect(tdObj.properties.device_name.forms[0]).not.to.have.property("security");
245243

246244
// check property soc
@@ -400,6 +398,7 @@ class AssetInterfaceDescriptionUtilTest {
400398
let hasContentType = false;
401399
let hasModbusFunction = false;
402400
let hasModbusType = false;
401+
let hasModbusMostSignificantByte = false;
403402
for (const formEntry of propProperty.value) {
404403
if (formEntry.idShort === "href") {
405404
hasHref = true;
@@ -410,7 +409,9 @@ class AssetInterfaceDescriptionUtilTest {
410409
// expect(formEntry.value).to.equal("readproperty");
411410
} else if (formEntry.idShort === "contentType") {
412411
hasContentType = true;
413-
expect(formEntry.value).to.equal("application/octet-stream");
412+
// Note: It uses the global contentType (locally it was not set in the AID)
413+
expect(formEntry.value).to.equal("text/plain");
414+
// expect(formEntry.value).to.equal("application/octet-stream");
414415
} else if (formEntry.idShort === "modv_function") {
415416
// vs. "modv:function"
416417
hasModbusFunction = true;
@@ -419,13 +420,18 @@ class AssetInterfaceDescriptionUtilTest {
419420
// vs. "modv:type"
420421
hasModbusType = true;
421422
expect(formEntry.value).to.equal("string");
423+
} else if (formEntry.idShort === "modv_mostSignificantByte") {
424+
// vs. "modv:mostSignificantByte"
425+
hasModbusMostSignificantByte = true;
426+
expect(formEntry.value).to.equal("true");
422427
}
423428
}
424429
expect(hasHref).to.equal(true);
425430
expect(hasOp).to.equal(false);
426431
expect(hasContentType).to.equal(true);
427432
expect(hasModbusFunction).to.equal(true);
428433
expect(hasModbusType).to.equal(true);
434+
expect(hasModbusMostSignificantByte).to.equal(true); // global
429435
}
430436
}
431437
expect(hasType).to.equal(true);
@@ -1045,6 +1051,201 @@ class AssetInterfaceDescriptionUtilTest {
10451051
expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true);
10461052
}
10471053

1054+
td3: ThingDescription = {
1055+
"@context": "https://www.w3.org/2022/wot/td/v1.1",
1056+
title: "ModbusTD",
1057+
securityDefinitions: {
1058+
nosec_sc: {
1059+
scheme: "nosec",
1060+
},
1061+
},
1062+
security: "nosec_sc",
1063+
base: "modbus+tcp://$$addr$$:502/$$unitid$$/",
1064+
properties: {
1065+
voltage: {
1066+
forms: [
1067+
{
1068+
href: "40001?quantity=2",
1069+
contentType: "application/octet-stream",
1070+
op: ["readproperty"],
1071+
"modv:function": "readHoldingRegisters",
1072+
"modv:type": "xsd:float",
1073+
"modv:mostSignificantByte": true,
1074+
"modv:mostSignificantWord": true,
1075+
},
1076+
],
1077+
},
1078+
},
1079+
};
1080+
1081+
@test async "should correctly transform sample TD3 into AID submodel"() {
1082+
const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td3));
1083+
1084+
const smObj = JSON.parse(sm);
1085+
// console.log("###\n\n" + JSON.stringify(smObj) + "\n\n###");
1086+
const isValid = this.validateAID(smObj);
1087+
expect(isValid.valid, isValid.errors).to.equal(true);
1088+
expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
1089+
expect(smObj).to.have.property("semanticId");
1090+
expect(smObj).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf.greaterThan(0);
1091+
const smInterface = smObj.submodelElements[0];
1092+
expect(smInterface).to.have.property("idShort").to.equal("InterfaceMODBUS_TCP"); // AID does not allow "+" in idShort, see InterfaceMODBUS+TCP
1093+
expect(smInterface).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0);
1094+
expect(smInterface)
1095+
.to.have.property("semanticId")
1096+
.to.be.an("object")
1097+
.with.property("keys")
1098+
.to.be.an("array")
1099+
.to.have.lengthOf.greaterThan(0);
1100+
expect(smInterface)
1101+
.to.have.property("supplementalSemanticIds")
1102+
.to.be.an("array")
1103+
.to.have.lengthOf.greaterThan(1); // default WoT-TD and http
1104+
let hasThingTitle = false;
1105+
let hasEndpointMetadata = false;
1106+
for (const smValue of smInterface.value) {
1107+
if (smValue.idShort === "title") {
1108+
hasThingTitle = true;
1109+
expect(smValue).to.have.property("value").to.equal("ModbusTD");
1110+
} else if (smValue.idShort === "EndpointMetadata") {
1111+
hasEndpointMetadata = true;
1112+
const endpointMetadata = smValue;
1113+
expect(endpointMetadata).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0);
1114+
let hasBase = false;
1115+
let hasContentType = false;
1116+
let hasSecurity = false;
1117+
let hasSecurityDefinitions = false;
1118+
for (const endpointMetadataValue of endpointMetadata.value) {
1119+
if (endpointMetadataValue.idShort === "base") {
1120+
hasBase = true;
1121+
expect(endpointMetadataValue.value).to.equal("modbus+tcp://$$addr$$:502/$$unitid$$/");
1122+
} else if (endpointMetadataValue.idShort === "contentType") {
1123+
hasContentType = true;
1124+
} else if (endpointMetadataValue.idShort === "security") {
1125+
hasSecurity = true;
1126+
expect(endpointMetadataValue)
1127+
.to.have.property("value")
1128+
.to.be.an("array")
1129+
.to.have.lengthOf.greaterThan(0);
1130+
expect(endpointMetadataValue.value[0]).to.have.property("value");
1131+
const modelReferenceValue = endpointMetadataValue.value[0].value;
1132+
expect(modelReferenceValue).to.have.property("type").to.equal("ModelReference");
1133+
expect(modelReferenceValue).to.have.property("keys").to.be.an("array").to.have.lengthOf(5);
1134+
expect(modelReferenceValue.keys[4]).to.have.property("value").to.equal("nosec_sc");
1135+
} else if (endpointMetadataValue.idShort === "securityDefinitions") {
1136+
hasSecurityDefinitions = true;
1137+
}
1138+
}
1139+
expect(hasBase).to.equal(true); // AID requires base to exist
1140+
expect(hasContentType).to.equal(false);
1141+
expect(hasSecurity).to.equal(true);
1142+
expect(hasSecurityDefinitions).to.equal(true);
1143+
}
1144+
}
1145+
expect(hasThingTitle, "No thing title").to.equal(true);
1146+
expect(hasEndpointMetadata, "No EndpointMetadata").to.equal(true);
1147+
1148+
// InteractionMetadata with properties etc
1149+
let hasInteractionMetadata = false;
1150+
for (const smValue of smInterface.value) {
1151+
if (smValue.idShort === "InteractionMetadata") {
1152+
hasInteractionMetadata = true;
1153+
expect(smValue).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0);
1154+
let hasProperties = false;
1155+
for (const interactionValues of smValue.value) {
1156+
if (interactionValues.idShort === "properties") {
1157+
hasProperties = true;
1158+
expect(interactionValues)
1159+
.to.have.property("value")
1160+
.to.be.an("array")
1161+
.to.have.lengthOf.greaterThan(0);
1162+
let hasPropertyVoltage = false;
1163+
for (const propertyValue of interactionValues.value) {
1164+
if (propertyValue.idShort === "voltage") {
1165+
hasPropertyVoltage = true;
1166+
expect(propertyValue)
1167+
.to.have.property("value")
1168+
.to.be.an("array")
1169+
.to.have.lengthOf.greaterThan(0);
1170+
let hasType = false;
1171+
let hasTitle = false;
1172+
let hasObservable = false;
1173+
let hasForms = false;
1174+
for (const propProperty of propertyValue.value) {
1175+
if (propProperty.idShort === "type") {
1176+
hasType = true;
1177+
} else if (propProperty.idShort === "title") {
1178+
hasTitle = true;
1179+
} else if (propProperty.idShort === "observable") {
1180+
hasObservable = true;
1181+
} else if (propProperty.idShort === "forms") {
1182+
hasForms = true;
1183+
expect(propProperty)
1184+
.to.have.property("value")
1185+
.to.be.an("array")
1186+
.to.have.lengthOf.greaterThan(0);
1187+
let hasHref = false;
1188+
let hasContentType = false;
1189+
let hasOp = false;
1190+
let hasModbusFunction = false;
1191+
let hasModbusType = false;
1192+
let hasModbusMostSignificantByte = false;
1193+
let hasModbusMostSignificantWord = false;
1194+
for (const formEntry of propProperty.value) {
1195+
if (formEntry.idShort === "href") {
1196+
hasHref = true;
1197+
expect(formEntry.value).to.be.oneOf([
1198+
"40001?quantity=2",
1199+
"modbus+tcp://$$addr$$:502/$$unitid$$/40001?quantity=2",
1200+
]); // absolute or relative
1201+
} else if (formEntry.idShort === "contentType") {
1202+
hasContentType = true;
1203+
expect(formEntry.value).to.equal("application/octet-stream");
1204+
} else if (formEntry.idShort === "op") {
1205+
hasOp = true;
1206+
// Note: AID does not know "op"
1207+
// expect(formEntry.value).to.equal("readproperty");
1208+
} else if (formEntry.idShort === "modv_function") {
1209+
hasModbusFunction = true;
1210+
expect(formEntry.value).to.equal("readHoldingRegisters");
1211+
} else if (formEntry.idShort === "modv_type") {
1212+
hasModbusType = true;
1213+
expect(formEntry.value).to.equal("xsd:float");
1214+
expect(formEntry.valueType).to.equal("xs:string");
1215+
} else if (formEntry.idShort === "modv_mostSignificantByte") {
1216+
hasModbusMostSignificantByte = true;
1217+
expect(formEntry.value).to.equal("true");
1218+
expect(formEntry.valueType).to.equal("xs:boolean");
1219+
} else if (formEntry.idShort === "modv_mostSignificantWord") {
1220+
hasModbusMostSignificantWord = true;
1221+
expect(formEntry.value).to.equal("true");
1222+
expect(formEntry.valueType).to.equal("xs:boolean");
1223+
}
1224+
}
1225+
expect(hasHref).to.equal(true);
1226+
expect(hasContentType).to.equal(true);
1227+
expect(hasOp).to.equal(false);
1228+
expect(hasModbusFunction).to.equal(true);
1229+
expect(hasModbusType).to.equal(true);
1230+
expect(hasModbusMostSignificantByte).to.equal(true);
1231+
expect(hasModbusMostSignificantWord).to.equal(true);
1232+
}
1233+
}
1234+
expect(hasType).to.equal(false);
1235+
expect(hasTitle).to.equal(false);
1236+
expect(hasObservable).to.equal(false);
1237+
expect(hasForms).to.equal(true);
1238+
}
1239+
}
1240+
expect(hasPropertyVoltage).to.equal(true);
1241+
}
1242+
}
1243+
expect(hasProperties).to.equal(true);
1244+
}
1245+
}
1246+
expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true);
1247+
}
1248+
10481249
@test.skip async "should correctly transform counter TD into JSON AAS"() {
10491250
// built-in fetch requires Node.js 18+
10501251
const response = await fetch("http://plugfest.thingweb.io:8083/counter");

packages/td-tools/test/util/AIDSchema.json

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,15 +1942,7 @@
19421942
"enum": ["xs:string"]
19431943
},
19441944
"value": {
1945-
"type": "string",
1946-
"enum": [
1947-
"application/json",
1948-
"application/octet-stream",
1949-
"application/pdf",
1950-
"application/rdf+xml",
1951-
"image/svg+xml",
1952-
"image/png;base64"
1953-
]
1945+
"type": "string"
19541946
},
19551947
"valueId": {
19561948
"$ref": "#/definitions/Reference"

0 commit comments

Comments
 (0)