Skip to content

Commit

Permalink
address space: emulate version103
Browse files Browse the repository at this point in the history
  - add the ability to generate 1.03 compatible address-space
    and export them to NodeSet2.xml
    ( 1.03 requires the old way to define DataType structure)
  - restore the ability to load old NodeSet2.xml even
    if nodes are set as Deprecated in the NodeSet2.xml file.
  • Loading branch information
erossignon committed Sep 27, 2022
1 parent 510df2e commit 53c85a4
Show file tree
Hide file tree
Showing 21 changed files with 2,045 additions and 96 deletions.
6 changes: 5 additions & 1 deletion packages/node-opcua-address-space-base/source/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ export declare interface INamespace {
namespaceUri: string;
addressSpace: IAddressSpace;
index: number;


$emulateVersion103?: boolean;

constructNodeId(options: ConstructNodeIdOptions): NodeId;

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -297,6 +299,8 @@ export declare interface INamespace {
addFolder(parentFolder: NodeIdLike | UAObject, options: any): UAObject;

createNode(options: CreateNodeOptions): BaseNode;

/** @private */
internalCreateNode(options: CreateNodeOptions): BaseNode;

// -------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import { IAddressSpace, INamespace } from "node-opcua-address-space-base";
import { ExtraDataTypeManager, populateDataTypeManager } from "node-opcua-client-dynamic-extension-object";
import { IAddressSpace, INamespace, UADataType } from "node-opcua-address-space-base";
import {
convertStructureTypeSchemaToStructureDefinition,
ExtraDataTypeManager,
populateDataTypeManager
} from "node-opcua-client-dynamic-extension-object";
import { make_debugLog } from "node-opcua-debug";
import { DataTypeFactory, getStandardDataTypeFactory } from "node-opcua-factory";
import { CallbackT } from "node-opcua-status-code";
import { StructureField } from "node-opcua-types";
import { AddressSpacePrivate } from "../../src/address_space_private";
import { PseudoSession } from "../pseudo_session";


const debugLog = make_debugLog(__filename);

interface UADataTypePriv extends UADataType {
$partialDefinition?: StructureField[];
}

function fixDefinition103(addressSpace: IAddressSpace, namespaceArray: string[], dataTypeManager: ExtraDataTypeManager) {
// fix datatype _getDefinition();
for (let namespaceIndex = 1; namespaceIndex < namespaceArray.length; namespaceIndex++) {
const df = dataTypeManager.getDataTypeFactory(namespaceIndex);
for (const s of df.getStructureIterator()) {
const dataType = addressSpace.findDataType(s.schema.dataTypeNodeId) as UADataTypePriv;
if (!s.constructor) {
continue;
}
if (!dataType) {
continue;
}
if (dataType.$partialDefinition && dataType.$partialDefinition.length) {
continue;
}
// debugLog(" Explorartion", dataType.browseName.toString());
if (!dataType.$partialDefinition || (dataType.$partialDefinition.length === 0 && s.schema.fields!.length > 0)) {
const sd = convertStructureTypeSchemaToStructureDefinition(s.schema);
dataType.$partialDefinition = sd.fields || undefined;
}
}
}
}

export async function ensureDatatypeExtracted(addressSpace: IAddressSpace): Promise<ExtraDataTypeManager> {
const addressSpacePriv: any = addressSpace as AddressSpacePrivate;
if (!addressSpacePriv.$$extraDataTypeManager) {
Expand All @@ -28,6 +60,9 @@ export async function ensureDatatypeExtracted(addressSpace: IAddressSpace): Prom
// now extract structure and enumeration from old form if
const session = new PseudoSession(addressSpace);
await populateDataTypeManager(session, dataTypeManager, true);

// turn old <=103 structure to have valid DataTypeDefinition
fixDefinition103(addressSpace, namespaceArray, dataTypeManager);
}
return addressSpacePriv.$$extraDataTypeManager;
}
Expand Down
56 changes: 38 additions & 18 deletions packages/node-opcua-address-space/source/loader/load_nodeset2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,15 @@ export interface NodeSet2ParserEngine {
terminate: (callback: SimpleCallback) => void;
}

export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2ParserEngine {
function makeNodeSetParserEngine(addressSpace: IAddressSpace, options: NodeSetLoaderOptions): NodeSet2ParserEngine {
const addressSpace1 = addressSpace as AddressSpacePrivate;
addressSpace1.suspendBackReference = true;

options.loadDeprecatedNodes = options.loadDeprecatedNodes === undefined ? true: options.loadDeprecatedNodes;
options.loadDraftNodes = options.loadDraftNodes || false;

const postTasks: Task[] = [];
const postTasks0_InitializeVariable: Task[] = [];
const postTasks0_DecodePojoString: Task[] = [];
const postTasks1_InitializeVariable: Task[] = [];
const postTasks2_AssignedExtensionObjectToDataValue: Task[] = [];
Expand Down Expand Up @@ -396,12 +400,10 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
this.obj.symbolicName = attrs.SymbolicName || null;

this.isDraft = attrs.ReleaseStatus === "Draft";
this.obj.isDeprecated = attrs.ReleaseStatus === "Deprecated";
this.isDeprecated = attrs.ReleaseStatus === "Deprecated";
},
finish(this: any) {
if (this.isDraft || this.isDeprecated) {
// ignore Draft or Deprecated element
debugLog("Ignoring Draft/Deprecated UAObject =", this.obj.browseName.toString());
if (canIngore({ isDraft: this.isDraft, isDeprecated: this.isDeprecated }, this.obj)) {
return;
}
_internal_createNode(this.obj);
Expand Down Expand Up @@ -510,9 +512,7 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
this.definitionFields = [];
},
finish(this: any) {
if (this.isDraft || this.isDeprecated) {
// ignore Draft or Deprecated element
debugLog("Ignoring Draft/Deprecated dataType =", this.obj.browseName.toString());
if (canIngore({ isDraft: this.isDraft, isDeprecated: this.isDeprecated }, this.obj)) {
return;
}
/*
Expand Down Expand Up @@ -1338,6 +1338,18 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
};
postTasks2_AssignedExtensionObjectToDataValue.push(task);
}

const canIngore = ({ isDraft, isDeprecated }: { isDraft: boolean; isDeprecated: boolean }, node: BaseNode) => {
if (isDraft && !options.loadDraftNodes) {
debugLog("Ignoring Draft =", NodeClass[node.nodeClass], node.browseName.toString());
return true;
}
if (isDeprecated && !options.loadDeprecatedNodes) {
debugLog("Ignoring Deprecate =", NodeClass[node.nodeClass], node.browseName.toString());
return true;
}
return false;
};
const state_UAVariable = {
init(this: any, name: string, attrs: XmlAttributes) {
_perform();
Expand Down Expand Up @@ -1365,10 +1377,10 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
this.isDeprecated = attrs.ReleaseStatus === "Deprecated";
},
finish(this: any) {
if (this.isDraft || this.isDeprecated) {
debugLog("Ignoring Draft/Deprecated UAVariable =", this.obj.browseName.toString());
if (canIngore({ isDraft: this.isDraft, isDeprecated: this.isDeprecated }, this.obj)) {
return;
}

/*
// set default value based on obj data Type
if (this.obj.value === undefined) {
Expand All @@ -1389,7 +1401,14 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
capturedValue = undefined;
(capturedVariable as any) = undefined;
};
postTasks1_InitializeVariable.push(task);
if (capturedValue.dataType !== DataType.ExtensionObject) {
postTasks0_InitializeVariable.push(task);
} else {
// do them later
postTasks1_InitializeVariable.push(task);
}


} else {
const task = async (addressSpace2: IAddressSpace) => {
const dataTypeNode = capturedVariable.dataType;
Expand All @@ -1407,7 +1426,7 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
}
(capturedVariable as any) = undefined;
};
postTasks1_InitializeVariable.push(task);
postTasks0_InitializeVariable.push(task);
}
this.obj.value = undefined;
capturedVariable = _internal_createNode(this.obj) as UAVariable;
Expand Down Expand Up @@ -1454,8 +1473,7 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
this.isDeprecated = attrs.ReleaseStatus === "Deprecated";
},
finish(this: any) {
if (this.isDraft || this.isDeprecated) {
debugLog("Ignoring Draft/Deprecated UAVariableType =", this.obj.browseName.toString());
if (canIngore({ isDraft: this.isDraft, isDeprecated: this.isDeprecated }, this.obj)) {
return;
}
try {
Expand Down Expand Up @@ -1501,8 +1519,7 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
this.isDeprecated = attrs.ReleaseStatus === "Deprecated";
},
finish(this: any) {
if (this.isDraft || this.isDeprecated) {
debugLog("Ignoring Draft/Deprecated UAMethod =", this.obj.browseName.toString());
if (canIngore({ isDraft: this.isDraft, isDeprecated: this.isDeprecated }, this.obj)) {
return;
}
_internal_createNode(this.obj);
Expand Down Expand Up @@ -1700,6 +1717,9 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
doDebug && debugLog(chalk.bgGreenBright("Performing post loading tasks -------------------------------------------"));
await performPostLoadingTasks(postTasks);

doDebug && debugLog(chalk.bgGreenBright("Performing post loading task: Initializing Simple Variables ---------------------"));
await performPostLoadingTasks(postTasks0_InitializeVariable);

doDebug && debugLog(chalk.bgGreenBright("Performing DataType extraction -------------------------------------------"));
assert(!addressSpace1.suspendBackReference);
await ensureDatatypeExtracted(addressSpace);
Expand All @@ -1719,7 +1739,7 @@ export function makeNodeSetParserEngine(addressSpace: IAddressSpace): NodeSet2Pa
doDebug && debugLog(chalk.bgGreenBright("Performing post loading task: Decoding Pojo String (parsing XML objects) -"));
await performPostLoadingTasks(postTasks0_DecodePojoString);

doDebug && debugLog(chalk.bgGreenBright("Performing post loading task: Initializing Variables ---------------------"));
doDebug && debugLog(chalk.bgGreenBright("Performing post loading task: Initializing Complex Variables ---------------------"));
await performPostLoadingTasks(postTasks1_InitializeVariable);

doDebug && debugLog(chalk.bgGreenBright("Performing post loading tasks: (assigning Extension Object to Variables) -"));
Expand Down Expand Up @@ -1754,7 +1774,7 @@ export interface NodeSetLoaderOptions {
export class NodeSetLoader {
_s: NodeSet2ParserEngine;
constructor(addressSpace: IAddressSpace, private options?: NodeSetLoaderOptions) {
this._s = makeNodeSetParserEngine(addressSpace);
this._s = makeNodeSetParserEngine(addressSpace, options || {});
}

addNodeSet(xmlData: string, callback: ErrorCallback): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ function _dumpVariantExtensionObjectValue(
// const encodingDefaultXml = (getStructureTypeConstructor(schema.name) as any).encodingDefaultXml;
const encodingDefaultXml = value.schema.encodingDefaultXml;
if (!encodingDefaultXml) {
warningLog("dataType Name ", name, "with ", dataTypeNodeId.toString(), " does not have xml encoding");
// throw new Error("Extension Object doesn't provide a XML ");
return;
}
Expand Down Expand Up @@ -513,7 +514,7 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, value: Var
}
assert(value instanceof Variant);

const dataTypeNode = addressSpace.findNode(node.dataType);
const dataTypeNode = addressSpace.findDataType(node.dataType);
if (!dataTypeNode) {
console.log("Cannot find dataType:", node.dataType);
return;
Expand All @@ -535,10 +536,6 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, value: Var
xw.startElement("Value");

if (isExtensionObject) {
const dataTypeNode = addressSpace.findDataType(node.dataType);
if (!dataTypeNode) {
throw new Error("Cannot find data type " + node.dataType.toString());
}

const encodeXml = _dumpVariantExtensionObjectValue2.bind(null, xw, dataTypeNode);

Expand Down
1 change: 1 addition & 0 deletions packages/node-opcua-address-space/src/ua_data_type_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ function makeEnumDefinition(definitionFields: EnumFieldOptions[]) {
}))
});
}

function makeStructureDefinition(
name: string,
baseDataType: NodeId,
Expand Down
1 change: 0 additions & 1 deletion packages/node-opcua-address-space/src/ua_object_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export class UAObjectImpl extends BaseNodeImpl implements UAObject {
}

public clone(options: CloneOptions, optionalFilter?: CloneFilter, extraInfo?: CloneExtraInfo): UAObject {
options = options || {};
options = {
...options,
eventNotifier: this.eventNotifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe("Testing loading nodeset with extension objects values in types", () =>
value.value.F2.should.eql([100, 200, 300]);
});

const x = (a: string) => a.replace(/^ +/gm, "");
const x = (a: string) => a.replace(/^ +/gm, "").split("\n");

it("LNEX4 - export back a nodeset2.xml file with dataType & enum as values", async () => {
await generateAddressSpace(addressSpace, [nodesets.standard, xml_file1]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require:
- ../../node_modules/source-map-support/register
- ../../node_modules/ts-node/register
- ../../node_modules/should
extension:
- ts
- js
recursive: true
timeout: 20000
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ async function resolve2(
};
if (category === FieldCategory.enumeration) {
if (definition instanceof EnumDefinition) {
const e = new EnumerationDefinitionSchema({
const e = new EnumerationDefinitionSchema(dataTypeNodeId, {
enumValues: convert(definition.fields),
name: fieldTypeName
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { CommonInterface, FieldCategory, FieldEnumeration, FieldType, IStructuredTypeSchema } from "node-opcua-factory";
import { StructureDefinition, StructureType, StructureDefinitionOptions, StructureFieldOptions } from "node-opcua-types";
import { NodeId, resolveNodeId } from "node-opcua-nodeid";
import { DataType } from "node-opcua-variant";

function _getDataType(field: FieldType): NodeId {
switch (field.category) {
case FieldCategory.complex:
return resolveNodeId((field.schema as any).dataTypeNodeId);
case FieldCategory.basic:
return resolveNodeId(field.fieldType);
case FieldCategory.enumeration:
default:
return resolveNodeId(field.dataType!);
}
}

export function convertStructureTypeSchemaToStructureDefinition(st: IStructuredTypeSchema): StructureDefinition {

let structureType = StructureType.Invalid;
let isUnion = false;
if (st.baseType === "Union") {
structureType = StructureType.Union;
isUnion = true;
} else {
structureType = StructureType.Structure;
}
// convert partial field (not including base class)
const structureDefinition: StructureDefinitionOptions = {
fields: [],
baseDataType: st.getBaseSchema()?.dataTypeNodeId,
defaultEncodingId: st.encodingDefaultBinary,
structureType
};
const fields: StructureFieldOptions[] = structureDefinition.fields || [];
for (const f of st.fields) {
const dataType = _getDataType(f);
if (isUnion && f.originalName === "SwitchField") {
continue;
}
fields.push({
arrayDimensions: f.isArray ? [] : undefined,
valueRank: f.isArray ? 1 : -1,
dataType,
isOptional: isUnion ? undefined: f.switchValue !== undefined,
description: f.documentation || undefined,
name: f.originalName
});
}
return new StructureDefinition(structureDefinition);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export * from "./get_extension_object_constructor";
export * from "./get_extra_data_type_manager";
export * from "./resolve_dynamic_extension_object";
export * from "./convert_data_type_definition_to_structuretype_schema";
export * from "./convert_structuretype_schema_to_structure_definition";
export * from "./promote_opaque_structure_in_notification_data";

0 comments on commit 53c85a4

Please sign in to comment.