Skip to content

Commit

Permalink
fix getter with ExtensionOject not exposing correct dataValue statusCode
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Mar 20, 2024
1 parent 3e1e4a5 commit 0fb5fcf
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 8 deletions.
24 changes: 20 additions & 4 deletions packages/node-opcua-address-space/src/ua_variable_impl.ts
Expand Up @@ -403,14 +403,30 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
if (this._timestamped_get_func) {
if (this._timestamped_get_func.length === 0) {
const dataValueOrPromise = (this._timestamped_get_func as VariableDataValueGetterSync)();
if (!Object.prototype.hasOwnProperty.call(dataValueOrPromise, "then")) {
if (!Object.prototype.hasOwnProperty.call(dataValueOrPromise.constructor.prototype, "then")) {
if (dataValueOrPromise !== this.$dataValue) {
// we may have a problem here if we use a getter that returns a dataValue that is a ExtensionObject
if (dataValueOrPromise.value?.dataType === DataType.ExtensionObject) {
// eslint-disable-next-line max-depth
if (this.$extensionObject || this.$$extensionObjectArray) {
// we have an extension object already bound to this node
// the client is asking us to replace the object entirely by a new one
// const ext = dataValue.value.value;
this._internal_set_dataValue(dataValueOrPromise);
return dataValueOrPromise;
}
}

// TO DO : is this necessary ? this may interfere with current use of $dataValue
this.$dataValue = dataValueOrPromise as DataValue;
this.verifyVariantCompatibility(this.$dataValue.value);
if (this.$dataValue.statusCode.isGoodish()) {
this.verifyVariantCompatibility(this.$dataValue.value);
}
}
} else {
errorLog("Unsupported: _timestamped_get_func returns a Promise !");
errorLog(
"[NODE-OPCUA-E28] Unsupported: _timestamped_get_func returns a Promise ! , when the uaVariable has an async getter. Fix your application code."
);
}
}
}
Expand Down Expand Up @@ -2099,7 +2115,7 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOpt
!this.$dataValue.statusCode.isGoodish() ||
!sameVariant(this.$dataValue.value, value as Variant)
) {
// rebuilding artificially timestamps with current clock as they are not provided
// rebuilding artificially timestamps with current clock as they are not provided
// by the underlying getter function
const { timestamp: sourceTimestamp, picoseconds: sourcePicoseconds } = getCurrentClock();
const serverTimestamp = sourceTimestamp;
Expand Down
Expand Up @@ -108,7 +108,7 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
property.$dataValue.sourcePicoseconds = now.picoseconds;
property.$dataValue.serverTimestamp = now.timestamp;
property.$dataValue.serverPicoseconds = now.picoseconds;
property.$dataValue.statusCode = StatusCodes.Good;
// don't change statusCode ! property.$dataValue.statusCode = StatusCodes.Good;
if (property.listenerCount("value_changed") > 0) {
property.emit("value_changed", property.$dataValue.clone());
}
Expand Down
92 changes: 89 additions & 3 deletions packages/node-opcua-address-space/test/test_variable.ts
Expand Up @@ -1291,6 +1291,7 @@ describe("testing UAVariable ", () => {
dataType: "Double",
nodeId: "ns=1;s=BadVar2",
organizedBy: rootFolder,
minimumSamplingInterval: 100,
value: {
refreshFunc(callback: CallbackT<DataValue>) {
throw new Error("Something goes wrong here! (intentional error for testing purpose)");
Expand Down Expand Up @@ -1442,7 +1443,92 @@ describe("testing UAVariable ", () => {
const statusCode1 = await temperatureVar.writeAttribute(context, writeValue);
statusCode1.should.eql(StatusCodes.Good);
});

it("ExtensionObject Variable with getter and Bad StatusCode (1)", async () => {
const objectsFolder = addressSpace.findNode("ObjectsFolder")!;
const uaExtObjVariable = namespace.addVariable({
browseName: "MyExtensionObjectVariable",

organizedBy: objectsFolder,
dataType: DataType.ExtensionObject,
minimumSamplingInterval: 1000,

value: {
// Sync getter !
timestamped_get: () => {
return new DataValue({
statusCode: StatusCodes.BadNoData,
value: new Variant({ dataType: DataType.ExtensionObject, value: null })
});
}
}
});

const dataValue = uaExtObjVariable.readValue();
const dataValue2 = await new Promise<DataValue>((resolve) => {
uaExtObjVariable.readValueAsync(context, (err, dataValue) => {
resolve(dataValue!);
});
});

dataValue.statusCode.should.eql(StatusCodes.BadNoData);
dataValue2.statusCode.should.eql(StatusCodes.BadNoData);
});
it("ExtensionObject Variable with getter and Bad StatusCode (2)", async () => {
const objectsFolder = addressSpace.findNode("ObjectsFolder")!;
const uaExtObjVariable = namespace.addVariable({
browseName: "MyExtensionObjectVariable",

organizedBy: objectsFolder,
dataType: DataType.ExtensionObject,
minimumSamplingInterval: 1000,

value: {
timestamped_get: async () => {
// async getter
return new DataValue({
statusCode: StatusCodes.BadNoData,
value: new Variant({ dataType: DataType.ExtensionObject, value: null })
});
}
}
});

const dataValue2 = await new Promise<DataValue>((resolve) => {
uaExtObjVariable.readValueAsync(context, (err, dataValue) => {
resolve(dataValue!);
});
});

dataValue2.statusCode.should.eql(StatusCodes.BadNoData);

// note : we cannot use sync readValue because the getter is async
//xx const dataValue = uaExtObjVariable.readValue();
//xx dataValue.statusCode.should.eql(StatusCodes.BadNoData);
});
it("ExtensionObject Variable with getter and Bad StatusCode (3)", async () => {
const objectsFolder = addressSpace.findNode("ObjectsFolder")!;
const uaExtObjVariable = namespace.addVariable({
browseName: "MyExtensionObjectVariable",

organizedBy: objectsFolder,
dataType: DataType.ExtensionObject,
minimumSamplingInterval: 1000,
value: {
timestamped_get: async () => {
return new DataValue({
statusCode: StatusCodes.BadNoData
// NO VALUE
});
}
}
});

const dataValue2 = await new Promise<DataValue>((resolve) => {
uaExtObjVariable.readValueAsync(context, (err, dataValue) => {
resolve(dataValue!);
});
});
dataValue2.statusCode.should.eql(StatusCodes.BadNoData);
});
});
function geCurrentClock(): import("node-opcua-date-time").PreciseClock {
throw new Error("Function not implemented.");
}

0 comments on commit 0fb5fcf

Please sign in to comment.