From 835759fe7f8cced4855920d1240c85f5a970f548 Mon Sep 17 00:00:00 2001 From: Etienne Rossignon Date: Tue, 26 May 2020 20:05:57 +0200 Subject: [PATCH] fix monitored item sample and object comparaison --- .../node-opcua-address-space/package.json | 3 +- .../node-opcua-address-space/src/base_node.ts | 2 - .../src/ua_variable.ts | 1 - ...st_e2e_monitored_item_extension_objects.ts | 2 +- .../u_test_e2e_SubscriptionUseCase.js | 38 +++++--- .../end_to_end/u_test_e2e_ctt_5.10.5_test3.js | 37 ++++---- ...est_e2e_monitored_item_semantic_changed.js | 86 +++++++++---------- .../source/monitored_item.ts | 13 ++- packages/node-opcua-variant/source/variant.ts | 5 ++ 9 files changed, 109 insertions(+), 78 deletions(-) diff --git a/packages/node-opcua-address-space/package.json b/packages/node-opcua-address-space/package.json index cbfe95dd6e..d372fb6fe3 100644 --- a/packages/node-opcua-address-space/package.json +++ b/packages/node-opcua-address-space/package.json @@ -46,7 +46,6 @@ "node-opcua-utils": "^2.6.0-alpha.1", "node-opcua-variant": "^2.6.0-alpha.1", "node-opcua-xml2json": "^2.6.0-alpha.1", - "object.values": "^1.1.1", "pretty-error": "^2.1.1", "set-prototype-of": "^1.0.0", "thenify": "^3.3.0", @@ -85,4 +84,4 @@ ], "homepage": "http://node-opcua.github.io/", "gitHead": "07dcdd8e8c7f2b55544c6e23023093e35674829c" -} +} \ No newline at end of file diff --git a/packages/node-opcua-address-space/src/base_node.ts b/packages/node-opcua-address-space/src/base_node.ts index 2d80dd34a6..15e0bf5513 100644 --- a/packages/node-opcua-address-space/src/base_node.ts +++ b/packages/node-opcua-address-space/src/base_node.ts @@ -65,8 +65,6 @@ import { MinimalistAddressSpace, Reference } from "./reference"; // tslint:disable:no-bitwise // tslint:disable:no-console -require("object.values"); - const doDebug = false; function defaultBrowseFilterFunc(context?: SessionContext): boolean { diff --git a/packages/node-opcua-address-space/src/ua_variable.ts b/packages/node-opcua-address-space/src/ua_variable.ts index 3b78e8c273..c832ca8ce6 100644 --- a/packages/node-opcua-address-space/src/ua_variable.ts +++ b/packages/node-opcua-address-space/src/ua_variable.ts @@ -617,7 +617,6 @@ export class UAVariable extends BaseNode implements UAVariablePublic { context = SessionContext.defaultContext; } - console.log("xyxy DataValue = ", dataValue.toString()); if (!dataValue.sourceTimestamp) { diff --git a/packages/node-opcua-end2end-test/test/end_to_end/test_e2e_monitored_item_extension_objects.ts b/packages/node-opcua-end2end-test/test/end_to_end/test_e2e_monitored_item_extension_objects.ts index 32ec47e67a..4f91c31674 100644 --- a/packages/node-opcua-end2end-test/test/end_to_end/test_e2e_monitored_item_extension_objects.ts +++ b/packages/node-opcua-end2end-test/test/end_to_end/test_e2e_monitored_item_extension_objects.ts @@ -112,8 +112,8 @@ describe("AZA1- testing Client-Server subscription use case, on a fake server ex try { const itemToMonitor = { + attributeId: AttributeIds.Value, nodeId, - attributeId: AttributeIds.Value }; const parameters: MonitoringParametersOptions = { queueSize: 10, diff --git a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_SubscriptionUseCase.js b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_SubscriptionUseCase.js index 8567e35b1d..d75ca1aee5 100644 --- a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_SubscriptionUseCase.js +++ b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_SubscriptionUseCase.js @@ -28,7 +28,7 @@ const perform_operation_on_monitoredItem = require("../../test_helpers/perform_o const Subscription = require("node-opcua-server").Subscription; -const doDebug = true; +const doDebug = false; const f = require("../../test_helpers/display_function_name").f.bind(null, true); function trace_console_log() { @@ -1126,6 +1126,7 @@ module.exports = function(test) { // - The second Publish contains a DataChangeNotification with a value.statusCode matching // the written value (and value.value matching the value before the write). // - The third Publish contains no DataChangeNotifications. + // (Did not expect a dataChange since the values written were the same, i.e. unchanged.) perform_operation_on_subscription(client, endpointUrl, function(session, subscription, callback) { const notificationMessageSpy = new sinon.spy(); @@ -1135,6 +1136,7 @@ module.exports = function(test) { }); const monitoredItemOnChangedSpy = new sinon.spy(); + const subscription_raw_notificiationSpy = new sinon.spy(); subscription.publishingInterval.should.eql(100); @@ -1166,6 +1168,8 @@ module.exports = function(test) { }); monitoredItem.on("changed", monitoredItemOnChangedSpy); + + subscription.on("raw_notification", subscription_raw_notificiationSpy); } @@ -1214,9 +1218,11 @@ module.exports = function(test) { create_monitored_item.bind(null), wait.bind(null, 300), + // - Write a status code to the Value attribute (don’t change the value of the Value attribute). write.bind(null, 1, StatusCodes.GoodWithOverflowBit), wait.bind(null, 300), + // - Write the existing value and status code to the Value attribute. write.bind(null, 1, StatusCodes.GoodWithOverflowBit), wait.bind(null, 300), @@ -1224,14 +1230,13 @@ module.exports = function(test) { // wait until next notification received; const lambda = (response) => { - console.log("response: ", response.constructor.name) + console.log("response: ", response.constructor.name, "notificationData.length", response.notificationMessage.notificationData.length); if (response.constructor.name === "PublishResponse") { client.removeListener("receive_response", lambda); // console.log(" xxxx ", response.toString()); - if (response.notificationMessage.notificationData.length !== 0) { - return callback(new Errro("Test has failed because PublishResponse has a unexpected notification data")) + return callback(new Error("Test has failed because PublishResponse has a unexpected notification data")) } callback(); } @@ -1241,16 +1246,29 @@ module.exports = function(test) { //xx wait.bind(null, subscription.publishingInterval * subscription.maxKeepAliveCount + 500), function(callback) { - monitoredItemOnChangedSpy.callCount.should.eql(2); - monitoredItemOnChangedSpy.getCall(0).args[0].statusCode.should.eql(StatusCodes.Good); - monitoredItemOnChangedSpy.getCall(1).args[0].statusCode.should.eql(StatusCodes.GoodWithOverflowBit); - - callback(); + try { + if (doDebug) { + console.log("subscription_raw_notificiationSpy = ", subscription_raw_notificiationSpy.callCount); + console.log("monitoredItemOnChangedSpy = ", monitoredItemOnChangedSpy.callCount); + for (let i = 0; i < monitoredItemOnChangedSpy.callCount; i++) { + console.log(" ", monitoredItemOnChangedSpy.getCall(i).args[0].statusCode.toString()); + } + } + monitoredItemOnChangedSpy.callCount.should.eql(2); + monitoredItemOnChangedSpy.getCall(0).args[0].statusCode.should.eql(StatusCodes.Good); + monitoredItemOnChangedSpy.getCall(1).args[0].statusCode.should.eql(StatusCodes.GoodWithOverflowBit); + callback(); + } catch (err) { + console.log(err); + callback(err); + } } ], callback); - }, done); + }, (err) => { + done(err); + }); }); diff --git a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_ctt_5.10.5_test3.js b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_ctt_5.10.5_test3.js index 2f11b4e013..50a2007aed 100644 --- a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_ctt_5.10.5_test3.js +++ b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_ctt_5.10.5_test3.js @@ -13,31 +13,39 @@ - * compare the published NotificationMessage to the republished NotificationMessage (should equal). */ -const assert = require("node-opcua-assert").assert; -const async = require("async"); +const { assert } = require("node-opcua-assert"); const should = require("should"); const sinon = require("sinon"); const opcua = require("node-opcua"); +const chalk = require("chalk"); -const OPCUAClient = opcua.OPCUAClient; +const { + OPCUAClient, + ClientSubscription +} = opcua; + +const doDebug = false; const { perform_operation_on_subscription_async } = require("../../test_helpers/perform_operation_on_client_session"); -async function f(func) { - await async function() { - debugLog(" * " + func.name.replace(/_/g, " ").replace(/(given|when|then)/, chalk.green("**$1**"))); - await func(); - debugLog(" ! " + func.name.replace(/_/g, " ").replace(/(given|when|then)/, chalk.green("**$1**"))); - - }(); +function f(func) { + const fct = async function(...args) { + if (doDebug) { + console.log(" * " + func.name.replace(/_/g, " ").replace(/(given|when|then)/, chalk.green("**$1**"))); + } + await func.apply(null, args); + if (doDebug) { + console.log(" ! " + func.name.replace(/_/g, " ").replace(/(given|when|then)/, chalk.green("**$1**"))); + } + } + return fct; } module.exports = function(test) { describe("Testing ctt ", function() { - const ClientSubscription = opcua.ClientSubscription; let subscription = null; const nodeId = "ns=2;s=Scalar_Static_Int32"; @@ -153,7 +161,6 @@ module.exports = function(test) { const response = await session.republish(request); //xx console.log(" xx = ",index,request.toString()); //xx console.log(" xx = ",index,response.toString()); - should.not.exist(err); response.notificationMessage.notificationData[0].monitoredItems[0].should.eql(expected_values[index]); } @@ -189,9 +196,9 @@ module.exports = function(test) { sequenceNumbers = [seqNumber1 + 1, seqNumber1 + 2, seqNumber1 + 3]; //xx console.log(expected_values, sequenceNumbers); - f(verify_republish)(session, 0); - f(verify_republish)(session, 1); - f(verify_republish)(session, 2); + await f(verify_republish)(session, 0); + await f(verify_republish)(session, 1); + await f(verify_republish)(session, 2); }); }); diff --git a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_monitored_item_semantic_changed.js b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_monitored_item_semantic_changed.js index 1fc8a6d0a2..a77d679981 100644 --- a/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_monitored_item_semantic_changed.js +++ b/packages/node-opcua-end2end-test/test/end_to_end/u_test_e2e_monitored_item_semantic_changed.js @@ -19,7 +19,7 @@ function getEURangeNodeId(session, nodeId, callback) { const browsePath = [ opcua.makeBrowsePath(nodeId, ".EURange") ]; - session.translateBrowsePath(browsePath, function (err, results) { + session.translateBrowsePath(browsePath, function(err, results) { if (!err) { euRangeNodeId = results[0].targets[0].targetId; @@ -35,14 +35,14 @@ function writeIncrement(session, analogDataItem, done) { let value = null; async.series([ - function (callback) { + function(callback) { const nodeToRead = { nodeId: analogDataItem, attributeId: opcua.AttributeIds.Value, indexRange: null, dataEncoding: null }; - session.read(nodeToRead, function (err, dataValue) { + session.read(nodeToRead, function(err, dataValue) { if (!err) { value = dataValue.value.value; } @@ -50,15 +50,15 @@ function writeIncrement(session, analogDataItem, done) { }); }, - function (callback) { + function(callback) { const nodeToWrite = { nodeId: analogDataItem, attributeId: opcua.AttributeIds.Value, value: new DataValue({ - value: {dataType: DataType.Double, value: value + 1} + value: { dataType: DataType.Double, value: value + 1 } }) }; - session.write(nodeToWrite, function (err, statusCode) { + session.write(nodeToWrite, function(err, statusCode) { statusCode.should.eql(opcua.StatusCodes.Good); callback(err); }); @@ -70,20 +70,20 @@ function readEURange(session, nodeId, done) { let euRangeNodeId; let euRange; async.series([ - function (callback) { - getEURangeNodeId(session, nodeId, function (err, result) { + function(callback) { + getEURangeNodeId(session, nodeId, function(err, result) { euRangeNodeId = result; callback(err); }); }, - function (callback) { + function(callback) { const nodesToRead = { nodeId: euRangeNodeId, attributeId: opcua.AttributeIds.Value, indexRange: null, dataEncoding: null }; - session.read(nodesToRead, function (err, dataValue) { + session.read(nodesToRead, function(err, dataValue) { if (!err) { euRange = dataValue.value.value; //xx console.log(" euRange =", euRange.toString()); @@ -92,7 +92,7 @@ function readEURange(session, nodeId, done) { }); } - ], function (results) { + ], function(results) { done(null, euRange) }); @@ -102,21 +102,21 @@ function writeEURange(session, nodeId, euRange, done) { let euRangeNodeId; async.series([ - function (callback) { - getEURangeNodeId(session, nodeId, function (err, result) { + function(callback) { + getEURangeNodeId(session, nodeId, function(err, result) { euRangeNodeId = result; callback(err); }); }, - function (callback) { + function(callback) { const nodeToWrite = { nodeId: euRangeNodeId, attributeId: opcua.AttributeIds.Value, value: new DataValue({ - value: {dataType: DataType.ExtensionObject, value: new Range(euRange)} + value: { dataType: DataType.ExtensionObject, value: new Range(euRange) } }) }; - session.write(nodeToWrite, function (err,statusCode) { + session.write(nodeToWrite, function(err, statusCode) { if (!err) { statusCode.should.eql(opcua.StatusCodes.Good); } @@ -129,19 +129,19 @@ function writeEURange(session, nodeId, euRange, done) { } -module.exports = function (test) { +module.exports = function(test) { - describe("Testing SemanticChanged Bit on statusCode monitoredItemData", function () { + describe("Testing SemanticChanged Bit on statusCode monitoredItemData", function() { let client, endpointUrl; - beforeEach(function (done) { + beforeEach(function(done) { client = OPCUAClient.create({}); endpointUrl = test.endpointUrl; done(); }); - afterEach(function (done) { + afterEach(function(done) { client.disconnect(done); client = null; }); @@ -152,14 +152,14 @@ module.exports = function (test) { const analogDataItem = "ns=2;s=DoubleAnalogDataItem"; - perform_operation_on_raw_subscription(client, endpointUrl, function (session, subscription, callback) { + perform_operation_on_raw_subscription(client, endpointUrl, function(session, subscription, callback) { let orgEURange = null; async.series([ // Read current Range - function (callback) { - readEURange(session, analogDataItem, function (err, euRange) { + function(callback) { + readEURange(session, analogDataItem, function(err, euRange) { if (!err) { orgEURange = euRange; } @@ -168,7 +168,7 @@ module.exports = function (test) { }, // - create Monitored Item - function (callback) { + function(callback) { const itemToMonitor = new opcua.ReadValueId({ attributeId: opcua.AttributeIds.Value, @@ -178,7 +178,7 @@ module.exports = function (test) { const monitoringParameters = new opcua.MonitoringParameters({ clientHandle: 1000, - samplingInterval: samplingInterval, + samplingInterval, filter: null, queueSize: 10, discardOldest: true @@ -195,11 +195,11 @@ module.exports = function (test) { const createMonitorItemsRequest = new opcua.CreateMonitoredItemsRequest({ subscriptionId: subscription.subscriptionId, timestampsToReturn: timestampsToReturn, - itemsToCreate: itemsToCreate + itemsToCreate }); //Xx console.log(createMonitorItemsRequest.toString()); - session.createMonitoredItems(createMonitorItemsRequest, function (err, response) { + session.createMonitoredItems(createMonitorItemsRequest, function(err, response) { //xx console.log("request=",createMonitorItemsRequest.toString()); //xx console.log("response = ",response.toString()); callback(err); @@ -208,12 +208,12 @@ module.exports = function (test) { }, // now get initial request - function (callback) { + function(callback) { const publish_request = new opcua.PublishRequest({ - requestHeader: {timeoutHint: 100000}, // see note + requestHeader: { timeoutHint: 100000 }, // see note subscriptionAcknowledgements: [] }); - session.publish(publish_request, function (err, publish_response) { + session.publish(publish_request, function(err, publish_response) { ///xx console.log(publish_response.toString()); // it should have the semantic changed bit set const monitoredData = publish_response.notificationMessage.notificationData[0].monitoredItems[0]; @@ -223,18 +223,18 @@ module.exports = function (test) { }, // Write modified range - function (callback) { - const newEURange = {low: orgEURange.low - 1, high: orgEURange.high + 1}; + function(callback) { + const newEURange = { low: orgEURange.low - 1, high: orgEURange.high + 1 }; writeEURange(session, analogDataItem, newEURange, callback); }, // now submit a publish request - function (callback) { + function(callback) { const publish_request = new opcua.PublishRequest({ - requestHeader: {timeoutHint: 100000}, // see note + requestHeader: { timeoutHint: 100000 }, // see note subscriptionAcknowledgements: [] }); - session.publish(publish_request, function (err, publish_response) { + session.publish(publish_request, function(err, publish_response) { if (err) { return callback(err); } @@ -247,17 +247,17 @@ module.exports = function (test) { }, // Write elements again to make sure we have a notification - function (callback) { + function(callback) { writeIncrement(session, analogDataItem, callback); }, // now submit a publish request - function (callback) { + function(callback) { const publish_request = new opcua.PublishRequest({ - requestHeader: {timeoutHint: 100000}, // see note + requestHeader: { timeoutHint: 100000 }, // see note subscriptionAcknowledgements: [] }); - session.publish(publish_request, function (err, publish_response) { + session.publish(publish_request, function(err, publish_response) { //xx console.log(publish_response.toString()); // it should have the semantic changed bit set const monitoredData = publish_response.notificationMessage.notificationData[0].monitoredItems[0]; @@ -268,7 +268,7 @@ module.exports = function (test) { // restore original range - function (callback) { + function(callback) { writeEURange(session, analogDataItem, orgEURange, callback); } ], callback) @@ -276,15 +276,15 @@ module.exports = function (test) { }, done); } - it("YY1 should set SemanticChanged - with sampling monitored item - 100 ms", function (done) { + it("YY1 should set SemanticChanged - with sampling monitored item - 100 ms", function(done) { check_semantic_change(100, done); }); - it("YY2 should set SemanticChanged - with event based monitored item", function (done) { + it("YY2 should set SemanticChanged - with event based monitored item", function(done) { check_semantic_change(0, done); }); - it("YY3 should set SemanticChanged - with sampling monitored item - 1000 ms", function (done) { + it("YY3 should set SemanticChanged - with sampling monitored item - 1000 ms", function(done) { check_semantic_change(1000, done); }); diff --git a/packages/node-opcua-server/source/monitored_item.ts b/packages/node-opcua-server/source/monitored_item.ts index 8ea0e6e204..4eb1543556 100644 --- a/packages/node-opcua-server/source/monitored_item.ts +++ b/packages/node-opcua-server/source/monitored_item.ts @@ -249,10 +249,15 @@ function apply_filter( } if (this.filter instanceof DataChangeFilter) { return apply_datachange_filter.call(this, newDataValue, this.oldDataValue); + } else { + // if filter not set, by default report changes to Status or Value only + if (newDataValue.statusCode.value !== this.oldDataValue.statusCode.value) { + return true; // Keep because statusCode has changed ... + } + return !sameVariant(newDataValue.value, this.oldDataValue.value); } return true; // keep // else { - // // if filter not set, by default report changes to Status or Value only // return !sameDataValue(newDataValue, this.oldDataValue, TimestampsToReturn.Neither); // } // return true; // keep @@ -736,9 +741,9 @@ export class MonitoredItem extends EventEmitter { console.log(" SAMPLING ERROR =>", err); } else { // only record value if source timestamp is newer - if (newDataValue && isSourceNewerThan(newDataValue, this.oldDataValue)) { - this._on_value_changed(newDataValue!); - } + // xx if (newDataValue && isSourceNewerThan(newDataValue, this.oldDataValue)) { + this._on_value_changed(newDataValue!); + // xx } } this._is_sampling = false; }); diff --git a/packages/node-opcua-variant/source/variant.ts b/packages/node-opcua-variant/source/variant.ts index d1a8d5a7a4..3e3a8f301f 100644 --- a/packages/node-opcua-variant/source/variant.ts +++ b/packages/node-opcua-variant/source/variant.ts @@ -1029,6 +1029,11 @@ export function sameVariant(v1: Variant, v2: Variant): boolean { return true; } if (v1.arrayType === VariantArrayType.Scalar) { + + if (v1.dataType === DataType.ExtensionObject) { + // compare two extension objects + return _.isEqual(v1.value, v2.value); + } if (Array.isArray(v1.value) && Array.isArray(v2.value)) { return __check_same_array(v1.value, v2.value); }