Skip to content

Commit 5eb0ca4

Browse files
refactor(td-tools/AID): add AID JSON schema (#1157)
* feat: add AID JSON Schema * fix: use updated JSON schema * fix: JSON schema restricting to valueType to xs:string only * fix: JSON schema w.r.t. schema definitions restriction * refactor: add AID JSON schema validation to tests * refactor: update logging (for skipped test) * refactor: look for "base" in property forms if not available
1 parent cc46531 commit 5eb0ca4

7 files changed

Lines changed: 4191 additions & 187 deletions

File tree

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

Lines changed: 103 additions & 78 deletions
Large diffs are not rendered by default.

packages/td-tools/test/AssetInterfaceDescriptionTest.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,39 @@ import { AssetInterfaceDescriptionUtil } from "../src/util/asset-interface-descr
2020
import { promises as fs } from "fs";
2121
import { ThingDescription } from "wot-typescript-definitions";
2222

23+
import Ajv, { ValidateFunction, ErrorObject } from "ajv";
24+
import * as AIDSchema from "../test/util/AIDSchema.json";
25+
26+
const aidSchema = AIDSchema;
27+
const ajv = new Ajv({ strict: false });
28+
2329
@suite("tests to verify the Asset Interface Description Utils")
2430
class AssetInterfaceDescriptionUtilTest {
2531
private assetInterfaceDescriptionUtil = new AssetInterfaceDescriptionUtil();
2632

33+
aidValidator = ajv.compile(aidSchema) as ValidateFunction;
34+
35+
// Note: Should this be a functionality of the AID tool OR for the time beeing just a test/control
36+
validateAID(aidSubmodel: object): { valid: boolean; errors?: string } {
37+
const isValid = this.aidValidator(aidSubmodel);
38+
let errors;
39+
if (!isValid) {
40+
errors = this.aidValidator.errors?.map((o: ErrorObject) => o.message).join("\n");
41+
}
42+
return {
43+
valid: isValid,
44+
errors,
45+
};
46+
}
47+
2748
@test async "should correctly transform counterHTTP into a TD"() {
2849
const modelAID = (await fs.readFile("test/util/counterHTTP.json")).toString();
50+
51+
const modelAIDobj = JSON.parse(modelAID);
52+
expect(modelAIDobj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1);
53+
const isValid = this.validateAID(modelAIDobj.submodels[0]);
54+
expect(isValid.valid, isValid.errors).to.equal(true);
55+
2956
const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "bla"}`);
3057

3158
const tdObj = JSON.parse(td);
@@ -167,6 +194,12 @@ class AssetInterfaceDescriptionUtilTest {
167194

168195
@test async "should correctly transform inverterModbus into a TD"() {
169196
const modelAID = (await fs.readFile("test/util/inverterModbus.json")).toString();
197+
198+
const modelAIDobj = JSON.parse(modelAID);
199+
expect(modelAIDobj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1);
200+
const isValid = this.validateAID(modelAIDobj.submodels[0]);
201+
expect(isValid.valid, isValid.errors).to.equal(true);
202+
170203
const td = this.assetInterfaceDescriptionUtil.transformAAS2TD(modelAID, `{"title": "bla"}`);
171204

172205
const tdObj = JSON.parse(td);
@@ -196,7 +229,7 @@ class AssetInterfaceDescriptionUtilTest {
196229
.to.have.property("forms")
197230
.to.be.an("array")
198231
.to.have.lengthOf(1);
199-
expect(tdObj.properties.device_name.forms[0]).to.have.property("op").to.eql("readproperty");
232+
// expect(tdObj.properties.device_name.forms[0]).to.have.property("op").to.eql("readproperty"); // AID does not know "op"
200233
expect(tdObj.properties.device_name.forms[0])
201234
.to.have.property("href")
202235
.to.eql("modbus+tcp://192.168.178.146:502/1/40020?quantity=16");
@@ -233,7 +266,7 @@ class AssetInterfaceDescriptionUtilTest {
233266
.to.have.property("forms")
234267
.to.be.an("array")
235268
.to.have.lengthOf(1);
236-
expect(tdObj.properties.soc.forms[0]).to.have.property("op").to.eql("readproperty");
269+
// expect(tdObj.properties.soc.forms[0]).to.have.property("op").to.eql("readproperty"); // AID does not know "op"
237270
expect(tdObj.properties.soc.forms[0])
238271
.to.have.property("href")
239272
.to.eql("modbus+tcp://192.168.178.146:502/40361?quantity=1");
@@ -250,6 +283,8 @@ class AssetInterfaceDescriptionUtilTest {
250283
const aidOutput = this.assetInterfaceDescriptionUtil.transformTD2SM(td);
251284

252285
const smObj = JSON.parse(aidOutput);
286+
const isValid = this.validateAID(smObj);
287+
expect(isValid.valid, isValid.errors).to.equal(true);
253288
expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
254289
expect(smObj).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf.greaterThan(0);
255290
const smInterface = smObj.submodelElements[0];
@@ -370,7 +405,8 @@ class AssetInterfaceDescriptionUtilTest {
370405
expect(formEntry.value).to.equal("1/40020?quantity=16");
371406
} else if (formEntry.idShort === "op") {
372407
hasOp = true;
373-
expect(formEntry.value).to.equal("readproperty");
408+
// Note: AID does not know "op"
409+
// expect(formEntry.value).to.equal("readproperty");
374410
} else if (formEntry.idShort === "contentType") {
375411
hasContentType = true;
376412
expect(formEntry.value).to.equal("application/octet-stream");
@@ -385,7 +421,7 @@ class AssetInterfaceDescriptionUtilTest {
385421
}
386422
}
387423
expect(hasHref).to.equal(true);
388-
expect(hasOp).to.equal(true);
424+
expect(hasOp).to.equal(false);
389425
expect(hasContentType).to.equal(true);
390426
expect(hasModbusFunction).to.equal(true);
391427
expect(hasModbusType).to.equal(true);
@@ -435,7 +471,8 @@ class AssetInterfaceDescriptionUtilTest {
435471
expect(formEntry.value).to.equal("40361?quantity=1"); // use base
436472
} else if (formEntry.idShort === "op") {
437473
hasOp = true;
438-
expect(formEntry.value).to.equal("readproperty");
474+
// Note: AID does not know "op"
475+
// expect(formEntry.value).to.equal("readproperty");
439476
} else if (formEntry.idShort === "contentType") {
440477
hasContentType = true;
441478
expect(formEntry.value).to.equal("application/octet-stream");
@@ -450,7 +487,7 @@ class AssetInterfaceDescriptionUtilTest {
450487
}
451488
}
452489
expect(hasHref).to.equal(true);
453-
expect(hasOp).to.equal(true);
490+
expect(hasOp).to.equal(false);
454491
expect(hasContentType).to.equal(true);
455492
expect(hasModbusFunction).to.equal(true);
456493
expect(hasModbusType).to.equal(true);
@@ -521,6 +558,8 @@ class AssetInterfaceDescriptionUtilTest {
521558
const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td1), ["https"]);
522559

523560
const smObj = JSON.parse(sm);
561+
const isValid = this.validateAID(smObj);
562+
expect(isValid.valid, isValid.errors).to.equal(true);
524563
expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
525564
expect(smObj).to.have.property("id");
526565
expect(smObj).to.have.property("semanticId");
@@ -729,7 +768,7 @@ class AssetInterfaceDescriptionUtilTest {
729768
expect(propProperty.value).to.equal("number");
730769
} else if (propProperty.idShort === "description") {
731770
hasDescription = true;
732-
expect(propProperty.value).to.equal("Temperature value of the weather station");
771+
// Note: AID has description on upper level
733772
} else if (propProperty.idShort === "unit") {
734773
hasUnit = true;
735774
expect(propProperty.value).to.equal("degreeCelsius");
@@ -738,7 +777,7 @@ class AssetInterfaceDescriptionUtilTest {
738777
}
739778
}
740779
expect(hasType).to.equal(true);
741-
expect(hasDescription).to.equal(true);
780+
expect(hasDescription).to.equal(false);
742781
expect(hasUnit).to.equal(true);
743782
expect(hasForms).to.equal(true);
744783
}
@@ -811,7 +850,7 @@ class AssetInterfaceDescriptionUtilTest {
811850
href: "modbus+tcp://127.0.0.1:60000/1",
812851
op: "readproperty",
813852
"modbus:function": "readCoil",
814-
"modbus:address": 1,
853+
"modbus:pollingTime": 1,
815854
},
816855
],
817856
},
@@ -822,6 +861,9 @@ class AssetInterfaceDescriptionUtilTest {
822861
const sm = this.assetInterfaceDescriptionUtil.transformTD2SM(JSON.stringify(this.td2));
823862

824863
const smObj = JSON.parse(sm);
864+
// console.log("###\n\n" + JSON.stringify(smObj) + "\n\n###");
865+
const isValid = this.validateAID(smObj);
866+
expect(isValid.valid, isValid.errors).to.equal(true);
825867
expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
826868
expect(smObj).to.have.property("semanticId");
827869
expect(smObj).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf.greaterThan(0);
@@ -855,6 +897,7 @@ class AssetInterfaceDescriptionUtilTest {
855897
for (const endpointMetadataValue of endpointMetadata.value) {
856898
if (endpointMetadataValue.idShort === "base") {
857899
hasBase = true;
900+
expect(endpointMetadataValue.value).to.equal("modbus+tcp://127.0.0.1:60000");
858901
} else if (endpointMetadataValue.idShort === "contentType") {
859902
hasContentType = true;
860903
} else if (endpointMetadataValue.idShort === "security") {
@@ -872,7 +915,7 @@ class AssetInterfaceDescriptionUtilTest {
872915
hasSecurityDefinitions = true;
873916
}
874917
}
875-
expect(hasBase).to.equal(false);
918+
expect(hasBase).to.equal(true); // AID requires base to exist
876919
expect(hasContentType).to.equal(false);
877920
expect(hasSecurity).to.equal(true);
878921
expect(hasSecurityDefinitions).to.equal(true);
@@ -933,19 +976,20 @@ class AssetInterfaceDescriptionUtilTest {
933976
hasContentType = true;
934977
} else if (formEntry.idShort === "op") {
935978
hasOp = true;
936-
expect(formEntry.value).to.equal("readproperty");
979+
// Note: AID does not know "op"
980+
// expect(formEntry.value).to.equal("readproperty");
937981
} else if (formEntry.idShort === "modbus_function") {
938982
hasModbusFunction = true;
939983
expect(formEntry.value).to.equal("readCoil");
940-
} else if (formEntry.idShort === "modbus_address") {
984+
} else if (formEntry.idShort === "modbus_pollingTime") {
941985
hasModbusAddress = true;
942986
expect(formEntry.value).to.equal("1");
943987
expect(formEntry.valueType).to.equal("xs:int");
944988
}
945989
}
946990
expect(hasHref).to.equal(true);
947991
expect(hasContentType).to.equal(false);
948-
expect(hasOp).to.equal(true);
992+
expect(hasOp).to.equal(false);
949993
expect(hasModbusFunction).to.equal(true);
950994
expect(hasModbusAddress).to.equal(true);
951995
}
@@ -970,17 +1014,17 @@ class AssetInterfaceDescriptionUtilTest {
9701014
const response = await fetch("http://plugfest.thingweb.io:8083/counter");
9711015
const counterTD = await response.json();
9721016

973-
const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http", "coap"]);
1017+
const sm = this.assetInterfaceDescriptionUtil.transformTD2AAS(JSON.stringify(counterTD), ["http"]); // "coap"
1018+
console.log("XXX AAS\n\n" + sm + "\n\nXXX");
9741019

9751020
const aasObj = JSON.parse(sm);
9761021
// TODO proper AID submodel checks
977-
console.log("XXX\n\n");
978-
console.log(JSON.stringify(aasObj));
979-
console.log("\n\nXXX");
980-
9811022
expect(aasObj).to.have.property("assetAdministrationShells").to.be.an("array");
9821023
expect(aasObj).to.have.property("submodels").to.be.an("array").to.have.lengthOf(1);
9831024
const submodel = aasObj.submodels[0];
984-
expect(submodel).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf(2);
1025+
console.log("YYY AID\n\n" + JSON.stringify(submodel) + "\n\nYYY");
1026+
const isValid = this.validateAID(submodel);
1027+
expect(isValid.valid, isValid.errors).to.equal(true);
1028+
expect(submodel).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf(1);
9851029
}
9861030
}

packages/td-tools/test/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"compilerOptions": {
44
"rootDir": ".."
55
},
6-
"include": ["*.ts", "**/*.ts", "../src/**/*.ts"]
6+
"include": ["*.ts", "**/*.ts", "../src/**/*.ts", "**/AIDSchema.json"]
77
}

0 commit comments

Comments
 (0)