"use strict";
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const DataValue = exports.DataValue = require("../_generated_/_auto_generated_DataValue").DataValue;
const DataType = require("node-opcua-variant").DataType;
const VariantArrayType = require("node-opcua-variant").VariantArrayType;
const TimestampsToReturn = require("../schemas/TimestampsToReturn_enum").TimestampsToReturn;
const registerSpecialVariantEncoder = require("node-opcua-factory").registerSpecialVariantEncoder;
registerSpecialVariantEncoder(exports.DataValue);
const getCurrentClock = require("node-opcua-date-time").getCurrentClock;
const Variant = require("node-opcua-variant").Variant;
const sameVariant = require("node-opcua-variant/src/variant_tools").sameVariant;
function w(n){
return ("0000"+n).substr(-3);
}
DataValue.prototype.toString = function () {
function toMicroNanoPico(picoseconds) {
//xx picoseconds = 123456789;
return ""
+ w((picoseconds / 1000000 )>>0)
+ "."
+ w(((picoseconds % 1000000 )/ 1000 )>>0)
+ "."
+ w((picoseconds % 1000 )>>0);
// + " (" + picoseconds+ ")";
}
let str = "DataValue:";
if (this.value) {
str += "\n value: " + Variant.prototype.toString.apply(this.value);//this.value.toString();
} else {
str += "\n value: <null>";
}
str += "\n statusCode: " + (this.statusCode ? this.statusCode.toString() : "null");
str += "\n serverTimestamp: " + (this.serverTimestamp ? this.serverTimestamp.toISOString()
+ " $ " + toMicroNanoPico(this.serverPicoseconds)
: "null");//+ " " + (this.serverTimestamp ? this.serverTimestamp.getTime() :"-");
str += "\n sourceTimestamp: " + (this.sourceTimestamp ? this.sourceTimestamp.toISOString()
+ " $ " + toMicroNanoPico(this.sourcePicoseconds)
: "null");// + " " + (this.sourceTimestamp ? this.sourceTimestamp.getTime() :"-");
return str;
};
DataValue.prototype.clone = function () {
const self = this;
const tmp = new DataValue({
serverTimestamp: self.serverTimestamp,
sourceTimestamp: self.sourceTimestamp,
serverPicoseconds: self.serverPicoseconds,
sourcePicoseconds: self.sourcePicoseconds,
statusCode: self.statusCode,
value: self.value ? self.value.clone() : null
});
return tmp;
};
function _partial_clone(dataValue) {
const cloneDataValue = new DataValue(null);
cloneDataValue.value = dataValue.value;
cloneDataValue.statusCode = dataValue.statusCode;
return cloneDataValue;
}
function apply_timestamps(dataValue, timestampsToReturn, attributeId) {
assert(attributeId > 0);
assert(timestampsToReturn.hasOwnProperty("key"));
assert(dataValue instanceof DataValue);
assert(dataValue.hasOwnProperty("serverTimestamp"));
assert(dataValue.hasOwnProperty("sourceTimestamp"));
let cloneDataValue = null;
let now = null;
// apply timestamps
switch (timestampsToReturn) {
case TimestampsToReturn.Neither:
cloneDataValue = cloneDataValue || _partial_clone(dataValue);
break;
case TimestampsToReturn.Server:
cloneDataValue = cloneDataValue || _partial_clone(dataValue);
cloneDataValue.serverTimestamp = dataValue.serverTimestamp;
cloneDataValue.serverPicoseconds = dataValue.serverPicoseconds;
if (!cloneDataValue.serverTimestamp) {
now = now || getCurrentClock();
cloneDataValue.serverTimestamp = now.timestamp;
cloneDataValue.serverPicoseconds = now.picoseconds;
}
break;
case TimestampsToReturn.Source:
cloneDataValue = cloneDataValue || _partial_clone(dataValue);
cloneDataValue.sourceTimestamp = dataValue.sourceTimestamp;
cloneDataValue.sourcePicoseconds = dataValue.sourcePicoseconds;
break;
case TimestampsToReturn.Both:
//xxif (false && attributeId !== 13 && dataValue.sourceTimestamp && dataValue.serverTimestamp) {
//xx return dataValue;
//xx}
cloneDataValue = cloneDataValue || _partial_clone(dataValue);
cloneDataValue.serverTimestamp = dataValue.serverTimestamp;
cloneDataValue.serverPicoseconds = dataValue.serverPicoseconds;
if (!cloneDataValue.serverTimestamp) {
now = now || getCurrentClock();
cloneDataValue.serverTimestamp = now.timestamp;
cloneDataValue.serverPicoseconds = now.picoseconds;
}
cloneDataValue.sourceTimestamp = dataValue.sourceTimestamp;
cloneDataValue.sourcePicoseconds = dataValue.sourcePicoseconds;
break;
}
// unset sourceTimestamp unless AttributeId is Value
if (attributeId !== 13/*AttributeIds.Value*/) {
cloneDataValue.sourceTimestamp = null;
}
return cloneDataValue;
}
function apply_timestamps2(dataValue, timestampsToReturn, attributeId) {
assert(attributeId > 0);
assert(timestampsToReturn.hasOwnProperty("key"));
assert(dataValue instanceof DataValue);
assert(dataValue.hasOwnProperty("serverTimestamp"));
assert(dataValue.hasOwnProperty("sourceTimestamp"));
const cloneDataValue = new DataValue({});
cloneDataValue.value = dataValue.value;
cloneDataValue.statusCode = dataValue.statusCode;
const now = getCurrentClock();
// apply timestamps
switch (timestampsToReturn) {
case TimestampsToReturn.Server:
cloneDataValue.serverTimestamp = dataValue.serverTimestamp;
cloneDataValue.serverPicoseconds = dataValue.serverPicoseconds;
//xxif (true || !cloneDataValue.serverTimestamp) {
cloneDataValue.serverTimestamp = now.timestamp;
cloneDataValue.serverPicoseconds = now.picoseconds;
//xx}
break;
case TimestampsToReturn.Source:
cloneDataValue.sourceTimestamp = dataValue.sourceTimestamp;
cloneDataValue.sourcePicoseconds = dataValue.sourcePicoseconds;
break;
case TimestampsToReturn.Both:
cloneDataValue.serverTimestamp = dataValue.serverTimestamp;
cloneDataValue.serverPicoseconds = dataValue.serverPicoseconds;
//xxif (true || !cloneDataValue.serverTimestamp) {
cloneDataValue.serverTimestamp = now.timestamp;
cloneDataValue.serverPicoseconds = now.picoseconds;
//xx}
cloneDataValue.sourceTimestamp = dataValue.sourceTimestamp;
cloneDataValue.sourcePicoseconds = dataValue.sourcePicoseconds;
break;
}
// unset sourceTimestamp unless AttributeId is Value
if (attributeId !== 13/*AttributeIds.Value*/) {
cloneDataValue.sourceTimestamp = null;
}
return cloneDataValue;
}
exports.apply_timestamps = apply_timestamps;
/*
* @method _clone_with_array_replacement
* @param dataValue
* @param result
* @return {DataValue}
* @private
* @static
*/
function _clone_with_array_replacement(dataValue, result) {
const clonedDataValue = new DataValue({
statusCode: result.statusCode,
serverTimestamp: dataValue.serverTimestamp,
serverPicoseconds: dataValue.serverPicoseconds,
sourceTimestamp: dataValue.sourceTimestamp,
sourcePicoseconds: dataValue.sourcePicoseconds,
value: {
dataType: DataType.Null
}
});
clonedDataValue.value.dataType = dataValue.value.dataType;
clonedDataValue.value.arrayType = dataValue.value.arrayType;
clonedDataValue.value.dimensions = result.dimensions;
clonedDataValue.value.value =result.array;
return clonedDataValue;
}
function canRange(dataValue) {
return dataValue.value && ((dataValue.value.arrayType !== VariantArrayType.Scalar) ||
((dataValue.value.arrayType === VariantArrayType.Scalar) && (dataValue.value.dataType === DataType.ByteString)) ||
((dataValue.value.arrayType === VariantArrayType.Scalar) && (dataValue.value.dataType === DataType.String)));
}
/**
* return a deep copy of the dataValue by applying indexRange if necessary on Array/Matrix
* @param dataValue {DataValue}
* @param indexRange {NumericalRange}
* @return {DataValue}
*/
function extractRange(dataValue, indexRange) {
const variant = dataValue.value;
if (indexRange && canRange(dataValue)) {
// let's extract an array of elements corresponding to the indexRange
const result = indexRange.extract_values(variant.value, variant.dimensions);
dataValue = _clone_with_array_replacement(dataValue, result);
//xx console.log(" dataValue =",dataValue.toString());
} else {
// clone the whole data Value
dataValue = dataValue.clone();
}
return dataValue;
}
exports.extractRange = extractRange;
function sameDate(date1, date2) {
if (date1 === date2) {
return true;
}
if (date1 && !date2) {
return false;
}
if (!date1 && date2) {
return false;
}
return date1.getTime() === date2.getTime();
}
function sourceTimestampHasChanged(dataValue1, dataValue2) {
assert(dataValue1, "expecting valid dataValue1");
assert(dataValue2, "expecting valid dataValue2");
const hasChanged =
!sameDate(dataValue1.sourceTimestamp, dataValue2.sourceTimestamp) ||
(dataValue1.sourcePicoseconds !== dataValue2.sourcePicoseconds);
return hasChanged;
}
exports.sourceTimestampHasChanged = sourceTimestampHasChanged;
function serverTimestampHasChanged(dataValue1, dataValue2) {
assert(dataValue1, "expecting valid dataValue1");
assert(dataValue2, "expecting valid dataValue2");
const hasChanged =
!sameDate(dataValue1.serverTimestamp, dataValue2.serverTimestamp) ||
(dataValue1.serverPicoseconds !== dataValue2.serverPicoseconds);
return hasChanged;
}
exports.serverTimestampHasChanged = serverTimestampHasChanged;
function timestampHasChanged(dataValue1, dataValue2, timestampsToReturn) {
//TODO: timestampsToReturn = timestampsToReturn || { key: "Neither"};
if (!timestampsToReturn) {
return sourceTimestampHasChanged(dataValue1, dataValue2) || serverTimestampHasChanged(dataValue1, dataValue2);
}
switch (timestampsToReturn.key) {
case "Neither":
return false;
case "Both":
return sourceTimestampHasChanged(dataValue1, dataValue2) || serverTimestampHasChanged(dataValue1, dataValue2);
case "Source":
return sourceTimestampHasChanged(dataValue1, dataValue2);
default:
assert(timestampsToReturn.key === "Server");
return serverTimestampHasChanged(dataValue1, dataValue2);
}
// return sourceTimestampHasChanged(dataValue1,dataValue2) || serverTimestampHasChanged(dataValue1,dataValue2);
}
exports.timestampHasChanged = timestampHasChanged;
/**
* @method sameDataValue
* @param v1 {DataValue}
* @param v2 {DataValue}
* @param [timestampsToReturn {TimestampsToReturn}]
* @return {boolean} true if data values are identical
*/
function sameDataValue(v1, v2, timestampsToReturn) {
if (v1 === v2) {
return true;
}
if (v1 && !v2) {
return false;
}
if (v2 && !v1) {
return false;
}
if (v1.statusCode !== v2.statusCode) {
return false;
}
//
// For performance reason, sourceTimestamp is
// used to determine if a dataValue has changed.
// if sourceTimestamp and sourcePicoseconds are identical
// then we make the assumption that Variant value is identical too.
// This will prevent us to deep compare potential large arrays.
// but before this is possible, we need to implement a mechanism
// that ensure that date() is always strictly increasing
if ((v1.sourceTimestamp && v2.sourceTimestamp) && !sourceTimestampHasChanged(v1, v2)) {
return true;
}
if (timestampHasChanged(v1, v2, timestampsToReturn)) {
return false;
}
return sameVariant(v1.value, v2.value);
}
exports.sameDataValue = sameDataValue;