node-opcua-modeler 2.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.js +13 -0
- package/LICENSE +20 -0
- package/MyModelIds.csv +151 -0
- package/dist/addExtensionObjectDataType.d.ts +24 -0
- package/dist/addExtensionObjectDataType.js +198 -0
- package/dist/addExtensionObjectDataType.js.map +1 -0
- package/dist/build_model_inner.d.ts +17 -0
- package/dist/build_model_inner.js +41 -0
- package/dist/build_model_inner.js.map +1 -0
- package/dist/displayNodeElement.d.ts +5 -0
- package/dist/displayNodeElement.js +157 -0
- package/dist/displayNodeElement.js.map +1 -0
- package/dist/generate_markdown_doc.d.ts +6 -0
- package/dist/generate_markdown_doc.js +148 -0
- package/dist/generate_markdown_doc.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/promoteToMandatory.d.ts +8 -0
- package/dist/promoteToMandatory.js +98 -0
- package/dist/promoteToMandatory.js.map +1 -0
- package/dist/setNamespaceMetaData.d.ts +1 -0
- package/dist/setNamespaceMetaData.js +6 -0
- package/dist/setNamespaceMetaData.js.map +1 -0
- package/dist/symbol.d.ts +1 -0
- package/dist/symbol.js +3 -0
- package/dist/symbol.js.map +1 -0
- package/dist/tableHelper.d.ts +9 -0
- package/dist/tableHelper.js +61 -0
- package/dist/tableHelper.js.map +1 -0
- package/dist/to_cvs.d.ts +2 -0
- package/dist/to_cvs.js +12 -0
- package/dist/to_cvs.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/distNodeJS/build_documentation_to_file.d.ts +2 -0
- package/distNodeJS/build_documentation_to_file.js +27 -0
- package/distNodeJS/build_documentation_to_file.js.map +1 -0
- package/distNodeJS/build_model.d.ts +7 -0
- package/distNodeJS/build_model.js +22 -0
- package/distNodeJS/build_model.js.map +1 -0
- package/distNodeJS/index.d.ts +5 -0
- package/distNodeJS/index.js +20 -0
- package/distNodeJS/index.js.map +1 -0
- package/distNodeJS/symbol_cvs.d.ts +3 -0
- package/distNodeJS/symbol_cvs.js +64 -0
- package/distNodeJS/symbol_cvs.js.map +1 -0
- package/examples/make_model.ts +152 -0
- package/nodeJS.d.ts +1 -0
- package/nodeJS.js +1 -0
- package/package.json +53 -0
- package/readme.md +43 -0
- package/source/addExtensionObjectDataType.ts +241 -0
- package/source/build_model_inner.ts +45 -0
- package/source/displayNodeElement.ts +178 -0
- package/source/generate_markdown_doc.ts +156 -0
- package/source/index.ts +31 -0
- package/source/promoteToMandatory.ts +137 -0
- package/source/setNamespaceMetaData.ts +1 -0
- package/source/symbol.ts +1 -0
- package/source/tableHelper.ts +64 -0
- package/source/to_cvs.ts +9 -0
- package/source/types.ts +1 -0
- package/source_nodejs/build_documentation_to_file.ts +13 -0
- package/source_nodejs/build_model.ts +12 -0
- package/source_nodejs/index.ts +5 -0
- package/source_nodejs/symbol_cvs.ts +52 -0
- package/tsconfig_common.json +55 -0
- package/tsconfig_nodejs.json +25 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getPresetSymbolsFromCSV = exports.saveSymbolsToCSV = void 0;
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const parse = require("csv-parse");
|
|
15
|
+
const __1 = require("..");
|
|
16
|
+
// node 14 onward : import { readFile, writeFile } from "fs/promises";
|
|
17
|
+
const { readFile, writeFile } = fs.promises;
|
|
18
|
+
function saveSymbolsToCSV(csvFilename, symbols) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
yield writeFile(csvFilename, (0, __1.toCSV)(symbols), "utf-8");
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
exports.saveSymbolsToCSV = saveSymbolsToCSV;
|
|
24
|
+
function getPresetSymbolsFromCSV(csvFilename) {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
if (!fs.existsSync(csvFilename)) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const data = yield readFile(csvFilename, "utf-8");
|
|
31
|
+
const records = yield new Promise((resolve) => {
|
|
32
|
+
const output = [];
|
|
33
|
+
parse(data, {
|
|
34
|
+
cast: (value, context) => {
|
|
35
|
+
if (context.index === 1) {
|
|
36
|
+
return parseInt(value, 10);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.on("readable", function () {
|
|
42
|
+
let record = this.read();
|
|
43
|
+
while (record) {
|
|
44
|
+
output.push(record);
|
|
45
|
+
record = this.read();
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.on("end", () => {
|
|
49
|
+
resolve(output);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
return records;
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err instanceof Error) {
|
|
56
|
+
// tslint:disable-next-line: no-console
|
|
57
|
+
console.log("getPresetSymbols err = ", err.message);
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
exports.getPresetSymbolsFromCSV = getPresetSymbolsFromCSV;
|
|
64
|
+
//# sourceMappingURL=symbol_cvs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbol_cvs.js","sourceRoot":"","sources":["../source_nodejs/symbol_cvs.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,yBAAyB;AAEzB,mCAAmC;AAInC,0BAA2B;AAE3B,sEAAsE;AACtE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC;AAE5C,SAAsB,gBAAgB,CAAC,WAAmB,EAAE,OAAgB;;QACxE,MAAM,SAAS,CAAC,WAAW,EAAE,IAAA,SAAK,EAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1D,CAAC;CAAA;AAFD,4CAEC;AAED,SAAsB,uBAAuB,CAAC,WAAmB;;QAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE;YAC7B,OAAO,EAAE,CAAC;SACb;QACD,IAAI;YACA,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAElD,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,MAAM,GAAU,EAAE,CAAC;gBACzB,KAAK,CAAC,IAAI,EAAE;oBACR,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;wBACrB,IAAI,OAAO,CAAC,KAAK,KAAK,CAAC,EAAE;4BACrB,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;yBAC9B;wBACD,OAAO,KAAK,CAAC;oBACjB,CAAC;iBACJ,CAAC;qBACG,EAAE,CAAC,UAAU,EAAE;oBACZ,IAAI,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;oBACzB,OAAO,MAAM,EAAE;wBACX,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;wBACpB,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;qBACxB;gBACL,CAAC,CAAC;qBACD,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACZ,OAAO,CAAC,MAAM,CAAC,CAAC;gBACpB,CAAC,CAAC,CAAC;YACX,CAAC,CAAC,CAAC;YACH,OAAO,OAAkB,CAAC;SAC7B;QAAC,OAAO,GAAG,EAAE;YACV,IAAI,GAAG,YAAY,KAAK,EAAE;gBACtB,uCAAuC;gBACvC,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;aACvD;YACD,OAAO,EAAE,CAAC;SACb;IACL,CAAC;CAAA;AApCD,0DAoCC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// tslint:disable:no-console
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import {
|
|
5
|
+
AddressSpace,
|
|
6
|
+
DataType,
|
|
7
|
+
displayNodeElement,
|
|
8
|
+
nodesets,
|
|
9
|
+
promoteToMandatory,
|
|
10
|
+
setNamespaceMetaData,
|
|
11
|
+
Symbols,
|
|
12
|
+
UAObject,
|
|
13
|
+
UAVariable,
|
|
14
|
+
UAVariableT
|
|
15
|
+
} from "..";
|
|
16
|
+
|
|
17
|
+
import { getPresetSymbolsFromCSV, saveSymbolsToCSV, buildModel } from "../nodeJS";
|
|
18
|
+
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
|
|
19
|
+
|
|
20
|
+
import { createBoilerType } from "node-opcua-address-space/testHelpers";
|
|
21
|
+
|
|
22
|
+
// node 14 onward : import { writeFile } from "fs/promises";
|
|
23
|
+
const { writeFile } = fs.promises;
|
|
24
|
+
|
|
25
|
+
interface UABoilerTest extends UAObject {
|
|
26
|
+
deviceHealth: UAVariable;
|
|
27
|
+
manufacturer: UAVariableT<string, DataType.String>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const xmlFiles = [nodesets.standard, nodesets.di];
|
|
31
|
+
|
|
32
|
+
const namespaceUri = "http://acme.com/Boiler/V0";
|
|
33
|
+
const version = "1.0.0";
|
|
34
|
+
|
|
35
|
+
const nodesetFilename = "./MyModel.NodeSet2.xml";
|
|
36
|
+
const symbolFilename = "./MyModelIds.csv";
|
|
37
|
+
|
|
38
|
+
async function createModel(addressSpace: AddressSpace): Promise<void> {
|
|
39
|
+
const ns = addressSpace.getOwnNamespace();
|
|
40
|
+
|
|
41
|
+
const nsDI = addressSpace.getNamespaceIndex("http://opcfoundation.org/UA/DI/");
|
|
42
|
+
if (nsDI < 0) {
|
|
43
|
+
throw new Error("Cannot find DI namespace!");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const deviceSet = addressSpace.rootFolder.objects.getFolderElementByName(`DeviceSet`);
|
|
47
|
+
if (!deviceSet) {
|
|
48
|
+
throw new Error("Cannot find DeviceSet object!");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const deviceType = addressSpace.findObjectType("DeviceType", nsDI);
|
|
52
|
+
if (!deviceType) {
|
|
53
|
+
throw new Error("Cannot find DeviceType");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// construct namespace meta data
|
|
57
|
+
const metaData = setNamespaceMetaData(addressSpace.getOwnNamespace());
|
|
58
|
+
|
|
59
|
+
const boilerDeviceType = ns.addObjectType({
|
|
60
|
+
browseName: "BoilerDeviceType",
|
|
61
|
+
subtypeOf: deviceType
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
promoteToMandatory(boilerDeviceType, "Manufacturer", nsDI);
|
|
65
|
+
promoteToMandatory(boilerDeviceType, "DeviceHealth", nsDI);
|
|
66
|
+
const parameterSet = promoteToMandatory(boilerDeviceType, "ParameterSet", nsDI);
|
|
67
|
+
|
|
68
|
+
ns.addVariable({
|
|
69
|
+
browseName: "HumiditySetpoint",
|
|
70
|
+
componentOf: parameterSet,
|
|
71
|
+
dataType: "Double"
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
ns.addVariable({
|
|
75
|
+
browseName: "TemperatureSetpoint",
|
|
76
|
+
componentOf: parameterSet,
|
|
77
|
+
dataType: "Double"
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
createBoilerType(ns);
|
|
81
|
+
|
|
82
|
+
console.log(displayNodeElement(boilerDeviceType));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function buildModelFile() {
|
|
86
|
+
try {
|
|
87
|
+
const presetSymbols = await getPresetSymbolsFromCSV(symbolFilename);
|
|
88
|
+
|
|
89
|
+
const { xmlModel, symbols } = await buildModel({
|
|
90
|
+
createModel,
|
|
91
|
+
namespaceUri,
|
|
92
|
+
version,
|
|
93
|
+
xmlFiles,
|
|
94
|
+
// tslint:disable-next-line: object-literal-sort-keys
|
|
95
|
+
presetSymbols
|
|
96
|
+
});
|
|
97
|
+
// save model to a file
|
|
98
|
+
await writeFile(nodesetFilename, xmlModel, "utf-8");
|
|
99
|
+
|
|
100
|
+
await saveSymbolsToCSV(symbolFilename, symbols);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.log("Error", err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function testNamespace() {
|
|
107
|
+
const addressSpace = AddressSpace.create();
|
|
108
|
+
const xmlFiles1 = [nodesets.standard, nodesets.di, nodesetFilename];
|
|
109
|
+
await generateAddressSpace(addressSpace, xmlFiles1);
|
|
110
|
+
|
|
111
|
+
const nsDI = addressSpace.getNamespaceIndex("http://opcfoundation.org/UA/DI/");
|
|
112
|
+
if (nsDI < 0) {
|
|
113
|
+
throw new Error("Cannot find DI namespace!");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const deviceSet = addressSpace.rootFolder.objects.getFolderElementByName(`DeviceSet`);
|
|
117
|
+
if (!deviceSet) {
|
|
118
|
+
throw new Error("Cannot find DeviceSet object!");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const nsLRA = addressSpace.getNamespaceIndex("http://acme.com/Boiler/V0");
|
|
122
|
+
const boilerDeviceType = addressSpace.findObjectType("BoilerDeviceType", nsLRA);
|
|
123
|
+
if (!boilerDeviceType) {
|
|
124
|
+
throw new Error("cannot find boiler type");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const boiler = boilerDeviceType.instantiate({
|
|
128
|
+
browseName: "Boiler1",
|
|
129
|
+
organizedBy: deviceSet
|
|
130
|
+
}) as UABoilerTest;
|
|
131
|
+
|
|
132
|
+
boiler.deviceHealth.writeEnumValue("MAINTENANCE_REQUIRED");
|
|
133
|
+
boiler.manufacturer.setValueFromSource({ dataType: DataType.LocalizedText, value: { text: "STERFIVE" } });
|
|
134
|
+
|
|
135
|
+
console.log(displayNodeElement(boiler));
|
|
136
|
+
|
|
137
|
+
addressSpace.dispose();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
(async () => {
|
|
141
|
+
try {
|
|
142
|
+
await buildModelFile();
|
|
143
|
+
// now test the nodeset
|
|
144
|
+
await testNamespace();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err instanceof Error) {
|
|
147
|
+
console.log("err", err.message);
|
|
148
|
+
}
|
|
149
|
+
console.log(err);
|
|
150
|
+
}
|
|
151
|
+
console.log("done");
|
|
152
|
+
})();
|
package/nodeJS.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./distNodeJS";
|
package/nodeJS.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./distNodeJS");
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-opcua-modeler",
|
|
3
|
+
"version": "2.51.0",
|
|
4
|
+
"description": "pure nodejs OPCUA SDK - module - model",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -b",
|
|
9
|
+
"clean": "node -e \"require('rimraf').sync('dist');\"",
|
|
10
|
+
"test": "mocha"
|
|
11
|
+
},
|
|
12
|
+
"author": "Etienne Rossignon",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"chalk": "4.1.2",
|
|
16
|
+
"cli-table3": "^0.6.0",
|
|
17
|
+
"csv-parse": "4.16.3",
|
|
18
|
+
"node-opcua-address-space": "2.51.0",
|
|
19
|
+
"node-opcua-assert": "2.51.0",
|
|
20
|
+
"node-opcua-basic-types": "2.51.0",
|
|
21
|
+
"node-opcua-client-dynamic-extension-object": "2.51.0",
|
|
22
|
+
"node-opcua-constants": "2.51.0",
|
|
23
|
+
"node-opcua-data-model": "2.51.0",
|
|
24
|
+
"node-opcua-factory": "2.51.0",
|
|
25
|
+
"node-opcua-nodeid": "2.51.0",
|
|
26
|
+
"node-opcua-nodesets": "2.51.0",
|
|
27
|
+
"node-opcua-numeric-range": "2.51.0",
|
|
28
|
+
"node-opcua-schemas": "2.51.0",
|
|
29
|
+
"node-opcua-service-translate-browse-path": "2.51.0",
|
|
30
|
+
"node-opcua-status-code": "2.51.0",
|
|
31
|
+
"node-opcua-types": "2.51.0",
|
|
32
|
+
"node-opcua-variant": "2.51.0",
|
|
33
|
+
"node-opcua-xml2json": "2.51.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"node-opcua-leak-detector": "2.51.0",
|
|
37
|
+
"should": "^13.2.3"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git://github.com/node-opcua/node-opcua.git"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"OPCUA",
|
|
45
|
+
"opcua",
|
|
46
|
+
"m2m",
|
|
47
|
+
"iot",
|
|
48
|
+
"opc ua",
|
|
49
|
+
"internet of things"
|
|
50
|
+
],
|
|
51
|
+
"homepage": "http://node-opcua.github.io/",
|
|
52
|
+
"gitHead": "75feb111daf7ec65fa0111e4fa5beb8987fd4945"
|
|
53
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
## Node-OPCUA Modeler
|
|
2
|
+
|
|
3
|
+
Node-opcua-modeler provides a programatic way to conveniently create OPCUA model and nodeset files.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import {
|
|
7
|
+
AddressSpace,
|
|
8
|
+
buildModel,
|
|
9
|
+
nodesets
|
|
10
|
+
} from "node-opcua-modeler";
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
|
|
13
|
+
// the namespaceUri
|
|
14
|
+
const namespaceUri = "http://acme.com/Boiler/V0";
|
|
15
|
+
const version= "1.0.0";
|
|
16
|
+
|
|
17
|
+
// the nodeset file required by your model
|
|
18
|
+
const xmlFiles: string[] = [
|
|
19
|
+
nodesets.standard,
|
|
20
|
+
nodesets.di
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
async function createModel(addressSpace: AddressSpace): Promise<void> {
|
|
24
|
+
// create your model here !
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const { markdown, xmlModel, symbols } = await buildModel({
|
|
30
|
+
namespaceUri,
|
|
31
|
+
version,
|
|
32
|
+
xmlFiles,
|
|
33
|
+
createModel
|
|
34
|
+
});
|
|
35
|
+
// save model to a file
|
|
36
|
+
const nodesetFiename = "./MyModel.NodeSet2.xml";
|
|
37
|
+
await fs.promises.writeFile(nodesetFiename, xmlModel, "utf-8");
|
|
38
|
+
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.log("Error", err);
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
```
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Namespace,
|
|
3
|
+
PseudoSession,
|
|
4
|
+
UADataType,
|
|
5
|
+
UAObject,
|
|
6
|
+
UAVariable,
|
|
7
|
+
UAVariableT,
|
|
8
|
+
UAVariableType,
|
|
9
|
+
dumpToBSD,
|
|
10
|
+
UADataTypeDictionary,
|
|
11
|
+
DTDataTypeDefinition,
|
|
12
|
+
UAProperty
|
|
13
|
+
} from "node-opcua-address-space";
|
|
14
|
+
import assert from "node-opcua-assert";
|
|
15
|
+
import { convertDataTypeDefinitionToStructureTypeSchema, ExtraDataTypeManager } from "node-opcua-client-dynamic-extension-object";
|
|
16
|
+
import { coerceQualifiedName, LocalizedTextLike, NodeClass, QualifiedNameLike } from "node-opcua-data-model";
|
|
17
|
+
import { ConstructorFuncWithSchema } from "node-opcua-factory";
|
|
18
|
+
import { NodeId, resolveNodeId } from "node-opcua-nodeid";
|
|
19
|
+
import { createDynamicObjectConstructor } from "node-opcua-schemas";
|
|
20
|
+
import { StructureDefinition, StructureDefinitionOptions } from "node-opcua-types";
|
|
21
|
+
import { DataType, Variant } from "node-opcua-variant";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* create the deprecated DataTypeDictionary node that was
|
|
25
|
+
* used up to version 1.03
|
|
26
|
+
*/
|
|
27
|
+
export function getOrCreateDataTypeSystem(namespace: Namespace): UAObject {
|
|
28
|
+
const addressSpace = namespace.addressSpace;
|
|
29
|
+
|
|
30
|
+
const opcBinaryTypeSystem = addressSpace.findNode("OPCBinarySchema_TypeSystem") as UAObject;
|
|
31
|
+
/* istanbul ignore next */
|
|
32
|
+
if (!opcBinaryTypeSystem) {
|
|
33
|
+
throw new Error("Cannot find OPCBinarySchema_TypeSystem");
|
|
34
|
+
}
|
|
35
|
+
assert(opcBinaryTypeSystem.nodeId.toString() === "ns=0;i=93");
|
|
36
|
+
assert(opcBinaryTypeSystem.browseName.toString() === "OPC Binary");
|
|
37
|
+
|
|
38
|
+
return opcBinaryTypeSystem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getDataTypeDictionary(namespace: Namespace): UADataTypeDictionary<Buffer> {
|
|
42
|
+
const addressSpace = namespace.addressSpace;
|
|
43
|
+
|
|
44
|
+
const opcBinaryTypeSystem = getOrCreateDataTypeSystem(namespace);
|
|
45
|
+
assert(opcBinaryTypeSystem.nodeId.toString() === "ns=0;i=93");
|
|
46
|
+
|
|
47
|
+
const name = namespace.namespaceUri.replace(/.*:/, "");
|
|
48
|
+
|
|
49
|
+
const node: UAVariable = opcBinaryTypeSystem.getComponentByName(name) as UAVariable;
|
|
50
|
+
if (node) {
|
|
51
|
+
assert(node.nodeClass === NodeClass.Variable);
|
|
52
|
+
// already exits ....
|
|
53
|
+
return node as UADataTypeDictionary<Buffer>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const dataTypeDictionaryType = addressSpace.findVariableType("DataTypeDictionaryType");
|
|
57
|
+
/* istanbul ignore next */
|
|
58
|
+
if (!dataTypeDictionaryType) {
|
|
59
|
+
throw new Error("Cannot find DataTypeDictionaryType");
|
|
60
|
+
}
|
|
61
|
+
const dataTypeDictionary = dataTypeDictionaryType.instantiate({
|
|
62
|
+
browseName: name!,
|
|
63
|
+
description: `Collects the data type descriptions of ${namespace.namespaceUri}`,
|
|
64
|
+
|
|
65
|
+
componentOf: opcBinaryTypeSystem,
|
|
66
|
+
|
|
67
|
+
optionals: ["Deprecated", "DataTypeVersion", "NamespaceUri"]
|
|
68
|
+
}) as UADataTypeDictionary<Buffer>;
|
|
69
|
+
|
|
70
|
+
dataTypeDictionary.bindVariable({
|
|
71
|
+
get: () => {
|
|
72
|
+
const bsd = dumpToBSD(namespace);
|
|
73
|
+
return new Variant({
|
|
74
|
+
dataType: DataType.ByteString,
|
|
75
|
+
value: Buffer.from(bsd, "utf-8")
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const namespaceUriProp = dataTypeDictionary.getPropertyByName("NamespaceUri");
|
|
81
|
+
if (namespaceUriProp) {
|
|
82
|
+
namespaceUriProp.setValueFromSource({ dataType: DataType.String, value: namespace.namespaceUri });
|
|
83
|
+
}
|
|
84
|
+
const deprecatedProp = dataTypeDictionary.getPropertyByName("Deprecated");
|
|
85
|
+
if (deprecatedProp) {
|
|
86
|
+
deprecatedProp.setValueFromSource({ dataType: DataType.Boolean, value: true });
|
|
87
|
+
}
|
|
88
|
+
return dataTypeDictionary;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function addDataTypeDescription(namespace: Namespace, dataType: UADataType) {
|
|
92
|
+
const addressSpace = namespace.addressSpace;
|
|
93
|
+
|
|
94
|
+
const dataTypeDictionary = getDataTypeDictionary(namespace);
|
|
95
|
+
|
|
96
|
+
const dataTypeDescriptionType = addressSpace.findVariableType("DataTypeDescriptionType");
|
|
97
|
+
if (!dataTypeDescriptionType) {
|
|
98
|
+
throw new Error("Cannot find DataTypeDescriptionType");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const dataTypeDescription = dataTypeDescriptionType.instantiate({
|
|
102
|
+
browseName: dataType.browseName.name!,
|
|
103
|
+
componentOf: dataTypeDictionary
|
|
104
|
+
});
|
|
105
|
+
dataTypeDescription.setValueFromSource({
|
|
106
|
+
dataType: DataType.String,
|
|
107
|
+
value: dataType.browseName.name!
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return dataTypeDescription;
|
|
111
|
+
}
|
|
112
|
+
export interface ExtensionObjectDefinition {
|
|
113
|
+
browseName: QualifiedNameLike;
|
|
114
|
+
description: LocalizedTextLike;
|
|
115
|
+
isAbstract: boolean;
|
|
116
|
+
|
|
117
|
+
structureDefinition: StructureDefinitionOptions;
|
|
118
|
+
binaryEncoding?: NodeId;
|
|
119
|
+
xmlEncoding?: NodeId;
|
|
120
|
+
jsonEncoding?: NodeId;
|
|
121
|
+
|
|
122
|
+
subtypeOf?: UADataType;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function addExtensionObjectDataType(namespace: Namespace, options: ExtensionObjectDefinition): Promise<UADataType> {
|
|
126
|
+
const addressSpace = namespace.addressSpace;
|
|
127
|
+
|
|
128
|
+
// encodings
|
|
129
|
+
const dataTypeEncodingType = addressSpace.findObjectType("DataTypeEncodingType");
|
|
130
|
+
/* istanbul ignore next */
|
|
131
|
+
if (!dataTypeEncodingType) {
|
|
132
|
+
throw new Error("Cannot find DataTypeEncodingType");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* istanbul ignore next */
|
|
136
|
+
if (!options.browseName.toString().match(/DataType$/)) {
|
|
137
|
+
throw new Error("DataType name must end up with DataType ! " + options.browseName.toString());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const baseSuperType = "Structure";
|
|
141
|
+
const subtypeOf = addressSpace.findDataType(options.subtypeOf ? options.subtypeOf : baseSuperType)!;
|
|
142
|
+
|
|
143
|
+
const structureDefinition = options.structureDefinition;
|
|
144
|
+
structureDefinition.baseDataType = structureDefinition.baseDataType
|
|
145
|
+
? resolveNodeId(structureDefinition.baseDataType)
|
|
146
|
+
: resolveNodeId("Structure");
|
|
147
|
+
|
|
148
|
+
const dataType = namespace.createDataType({
|
|
149
|
+
browseName: options.browseName,
|
|
150
|
+
description: options.description,
|
|
151
|
+
isAbstract: options.isAbstract,
|
|
152
|
+
subtypeOf
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const defaultBinary = dataTypeEncodingType.instantiate({
|
|
156
|
+
browseName: coerceQualifiedName("0:Default Binary"),
|
|
157
|
+
encodingOf: dataType
|
|
158
|
+
// nodeId: defaultBinaryEncodingNode,
|
|
159
|
+
})!;
|
|
160
|
+
assert(defaultBinary.browseName.toString() === "Default Binary");
|
|
161
|
+
|
|
162
|
+
(dataType as any).$definition = new StructureDefinition(structureDefinition);
|
|
163
|
+
assert(!NodeId.sameNodeId((dataType as any).$definition.baseDataType, NodeId.nullNodeId));
|
|
164
|
+
|
|
165
|
+
const dataTypeDescription = addDataTypeDescription(namespace, dataType);
|
|
166
|
+
defaultBinary.addReference({
|
|
167
|
+
isForward: true,
|
|
168
|
+
nodeId: dataTypeDescription,
|
|
169
|
+
referenceType: "HasDescription"
|
|
170
|
+
});
|
|
171
|
+
const v = dataType.getEncodingNode("Default Binary")!;
|
|
172
|
+
assert(v?.browseName.toString() === "Default Binary");
|
|
173
|
+
|
|
174
|
+
/// --------------- Create constructor
|
|
175
|
+
const dataTypeManager = (addressSpace as any).$$extraDataTypeManager as ExtraDataTypeManager;
|
|
176
|
+
const dataTypeFactory = dataTypeManager.getDataTypeFactory(namespace.index);
|
|
177
|
+
const session = new PseudoSession(addressSpace);
|
|
178
|
+
|
|
179
|
+
const className = dataType.browseName.name!;
|
|
180
|
+
const cache: any = {};
|
|
181
|
+
const schema = await convertDataTypeDefinitionToStructureTypeSchema(
|
|
182
|
+
session,
|
|
183
|
+
dataType.nodeId,
|
|
184
|
+
className,
|
|
185
|
+
(dataType as any).$definition,
|
|
186
|
+
dataTypeFactory,
|
|
187
|
+
cache
|
|
188
|
+
);
|
|
189
|
+
const Constructor = createDynamicObjectConstructor(schema, dataTypeFactory) as ConstructorFuncWithSchema;
|
|
190
|
+
|
|
191
|
+
return dataType;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function addVariableTypeForDataType(namespace: Namespace, dataType: UADataType): UAVariableType {
|
|
195
|
+
const addressSpace = namespace.addressSpace;
|
|
196
|
+
|
|
197
|
+
// get Definition
|
|
198
|
+
const definition = (dataType as any).$definition as StructureDefinition;
|
|
199
|
+
if (!definition || !(definition instanceof StructureDefinition)) {
|
|
200
|
+
throw new Error("dataType is not a structure");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const variableTypeName = dataType.browseName.name?.replace("DataType", "Type")!;
|
|
204
|
+
const variableType = namespace.addVariableType({
|
|
205
|
+
browseName: variableTypeName,
|
|
206
|
+
dataType: dataType.nodeId
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const structure = addressSpace.findDataType("Structure")!;
|
|
210
|
+
for (const field of definition.fields || []) {
|
|
211
|
+
let typeDefinition: UAVariableType | string = "BaseVariableType";
|
|
212
|
+
const fType = addressSpace.findDataType(field.dataType);
|
|
213
|
+
/* istanbul ignore next */
|
|
214
|
+
if (!fType) {
|
|
215
|
+
throw new Error("Cannot find dataType" + field.dataType.toString());
|
|
216
|
+
}
|
|
217
|
+
if (fType.isSupertypeOf(structure)) {
|
|
218
|
+
const name = fType.browseName.name!.replace("DataType", "Type");
|
|
219
|
+
typeDefinition = addressSpace.findVariableType(name, field.dataType.namespace)!;
|
|
220
|
+
const comp = typeDefinition.instantiate({
|
|
221
|
+
browseName: field.name!,
|
|
222
|
+
componentOf: variableType,
|
|
223
|
+
dataType: field.dataType,
|
|
224
|
+
description: field.description,
|
|
225
|
+
modellingRule: "Mandatory",
|
|
226
|
+
valueRank: field.valueRank === undefined ? -1 : field.valueRank
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
const comp = namespace.addVariable({
|
|
230
|
+
browseName: field.name!,
|
|
231
|
+
componentOf: variableType,
|
|
232
|
+
dataType: field.dataType,
|
|
233
|
+
description: field.description,
|
|
234
|
+
modellingRule: "Mandatory",
|
|
235
|
+
typeDefinition,
|
|
236
|
+
valueRank: field.valueRank === undefined ? -1 : field.valueRank
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return variableType;
|
|
241
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AddressSpace,
|
|
3
|
+
Namespace, NodeIdManager, XmlLoaderFunc, generateAddressSpaceRaw } from "node-opcua-address-space";
|
|
4
|
+
import { buildDocumentationToString } from "./generate_markdown_doc";
|
|
5
|
+
import { Symbols } from "./symbol";
|
|
6
|
+
|
|
7
|
+
export interface BuildModelOptionsBase {
|
|
8
|
+
version: string;
|
|
9
|
+
namespaceUri: string;
|
|
10
|
+
xmlFiles: string[];
|
|
11
|
+
createModel: /*async*/ (addressSpace: AddressSpace) => Promise<void>;
|
|
12
|
+
presetSymbols?: Symbols;
|
|
13
|
+
}
|
|
14
|
+
export interface BuildModelOptions extends BuildModelOptionsBase {
|
|
15
|
+
xmlLoader: XmlLoaderFunc;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function buildModelInner(data: BuildModelOptions): Promise<{ markdown: string; xmlModel: string; symbols: Symbols }> {
|
|
19
|
+
try {
|
|
20
|
+
const addressSpace = AddressSpace.create();
|
|
21
|
+
|
|
22
|
+
// create own namespace (before loading other xml files)
|
|
23
|
+
const ns = addressSpace.registerNamespace(data.namespaceUri);
|
|
24
|
+
|
|
25
|
+
const nodeIdManager = (ns as any)._nodeIdManager as NodeIdManager;
|
|
26
|
+
if (data.presetSymbols) {
|
|
27
|
+
nodeIdManager.setSymbols(data.presetSymbols);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await generateAddressSpaceRaw(addressSpace, data.xmlFiles, data.xmlLoader);
|
|
31
|
+
|
|
32
|
+
await data.createModel(addressSpace);
|
|
33
|
+
|
|
34
|
+
const xmlModel = ns.toNodeset2XML();
|
|
35
|
+
const symbols = nodeIdManager.getSymbols();
|
|
36
|
+
const doc = await buildDocumentationToString(ns);
|
|
37
|
+
addressSpace.dispose();
|
|
38
|
+
|
|
39
|
+
return { xmlModel, symbols, markdown: doc };
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// tslint:disable-next-line: no-console
|
|
42
|
+
console.log("Error", err);
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|