Skip to content

Commit

Permalink
Merge dfb125e into 1be94dc
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Jan 29, 2023
2 parents 1be94dc + dfb125e commit ec021df
Show file tree
Hide file tree
Showing 23 changed files with 1,452 additions and 200 deletions.
@@ -1,11 +1,13 @@
import { QualifiedName } from "node-opcua-data-model";
import { DataValue } from "node-opcua-data-value";
import { ExtensionObject } from "node-opcua-extension-object";
import { UADataType } from "./ua_data_type";
import { UAVariable } from "./ua_variable";
import { UAVariableType } from "./ua_variable_type";

// {{ Dynamic Array Variable
export interface UADynamicVariableArray<T extends ExtensionObject= ExtensionObject> extends UAVariable {
$dataValue: DataValue;
$$variableType: UAVariableType;
$$dataType: UADataType;
$$extensionObjectArray: T[];
Expand Down
2 changes: 1 addition & 1 deletion packages/node-opcua-address-space/src/base_node_private.ts
Expand Up @@ -418,7 +418,7 @@ export function VariableOrVariableType_toString(this: UAVariableType | UAVariabl
options.add(options.padding + chalk.yellow(" dataType : ") + this.dataType + " " + n);
}
if (this.nodeClass === NodeClass.Variable) {
const _dataValue = (<WithDataValue>this).$dataValue as DataValue | undefined;
const _dataValue = (<WithDataValue>this).$dataValue;
if (_dataValue) {
options.add(
options.padding +
Expand Down
Expand Up @@ -43,6 +43,10 @@ function removeElementByIndex<T extends ExtensionObject>(uaArrayVariableNode: UA

// remove element from global array (inefficient)
uaArrayVariableNode.$$extensionObjectArray.splice(elementIndex, 1);
if(uaArrayVariableNode.$$extensionObjectArray !== uaArrayVariableNode.$dataValue.value.value) {
// throw new Error("internal error");
}
uaArrayVariableNode.touchValue();

// remove matching component
const node = uaArrayVariableNode.getComponentByName(browseName);
Expand Down Expand Up @@ -170,12 +174,13 @@ export function bindExtObjArrayNode<T extends ExtensionObject>(
// verify that an object with same doesn't already exist
dataType = addressSpace.findDataType(variableType.dataType)! as UADataType;
assert(dataType!.isSupertypeOf(structure), "expecting a structure (= ExtensionObject) here ");

assert(!uaArrayVariableNode.$$extensionObjectArray, "UAVariable ExtensionObject array already bounded");
uaArrayVariableNode.$$dataType = dataType;
uaArrayVariableNode.$$extensionObjectArray = [];
uaArrayVariableNode.$$indexPropertyName = indexPropertyName;

uaArrayVariableNode.$$getElementBrowseName = _getElementBrowseName;
uaArrayVariableNode.$dataValue.value.value = uaArrayVariableNode.$$extensionObjectArray;
uaArrayVariableNode.$dataValue.value.arrayType = VariantArrayType.Array;

const bindOptions: any = {
get: getExtObjArrayNodeValue,
Expand Down Expand Up @@ -246,8 +251,12 @@ export function addElement<T extends ExtensionObject>(
elVar.bindExtensionObject(extensionObject, { force: true });
}

if(uaArrayVariableNode.$$extensionObjectArray !== uaArrayVariableNode.$dataValue.value.value) {
// throw new Error("internal error");
}
// also add the value inside
uaArrayVariableNode.$$extensionObjectArray.push(elVar.$extensionObject);
uaArrayVariableNode.touchValue();

return elVar;
}
Expand Down
66 changes: 51 additions & 15 deletions packages/node-opcua-address-space/src/ua_variable_impl.ts
Expand Up @@ -86,6 +86,8 @@ import { apply_condition_refresh, ConditionRefreshCache } from "./apply_conditio
import {
extractPartialData,
incrementElement,
propagateTouchValueDownward,
propagateTouchValueDownwardArray,
propagateTouchValueUpward,
setExtensionObjectPartialValue,
_bindExtensionObject,
Expand Down Expand Up @@ -524,6 +526,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {

public isExtensionObject(): boolean {
// DataType must be one of Structure
if (this.dataType.isEmpty()) return false;
const dataTypeNode = this.addressSpace.findDataType(this.dataType) as UADataType;
if (!dataTypeNode) {
throw new Error(" Cannot find DataType " + this.dataType.toString() + " in standard address Space");
Expand Down Expand Up @@ -572,9 +575,9 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
dataValue.serverPicoseconds = 0;
return callback(null, dataValue);
}

try {
this.refreshFunc.call(this, (err: Error | null, dataValue?: DataValueLike) => {
// istanbul ignore next
if (err || !dataValue) {
errorLog(
"-------------- refresh call failed",
Expand Down Expand Up @@ -819,14 +822,15 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
errorLog(dataValue.toString());
this.checkExtensionObjectIsCorrect(dataValue.value.value);
}
this.$dataValue = dataValue;
// ----------------------------------
if (this.$extensionObject) {
// we have an extension object already bound to this node
// the client is asking us to replace the object entierly by a new one
// const ext = dataValue.value.value;
this._internal_set_dataValue(dataValue);
return;
} else {
this.$dataValue = dataValue;
}
} else {
this._internal_set_dataValue(dataValue);
Expand Down Expand Up @@ -1283,6 +1287,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
const n = callbacks.length;
for (const callback1 of callbacks) {
callback1.call(this, err, dataValue);

}
};

Expand Down Expand Up @@ -1712,7 +1717,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
throw new Error("Invalid Extension Object on nodeId =" + this.nodeId.toString());
}
}

this.verifyVariantCompatibility(dataValue.value);

this._inner_replace_dataValue(dataValue, indexRange);
Expand All @@ -1722,23 +1727,54 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
* @private
*/
public _inner_replace_dataValue(dataValue: DataValue, indexRange?: NumericRange | null) {
const old_dataValue = this.$dataValue;

this.$dataValue = dataValue;
this.$dataValue.statusCode = this.$dataValue.statusCode || StatusCodes.Good;
assert(this.$dataValue.value instanceof Variant);
const old_dataValue = this.$dataValue.clone();

if (this.$$extensionObjectArray && dataValue.value.arrayType !== VariantArrayType.Scalar) {
// we have a bounded array or matrix
assert(Array.isArray(dataValue.value.value));
if (this.$$extensionObjectArray !== this.$dataValue.value.value) {
throw new Error("internal error");
}
this.$$extensionObjectArray = dataValue.value.value;
this.$dataValue.value.value = dataValue.value.value;

this.$dataValue.statusCode = dataValue.statusCode || StatusCodes.Good;
this.$dataValue.serverTimestamp = dataValue.serverTimestamp;
this.$dataValue.serverPicoseconds = dataValue.serverPicoseconds;
this.$dataValue.sourceTimestamp = dataValue.sourceTimestamp;
this.$dataValue.sourcePicoseconds = dataValue.sourcePicoseconds;

} else {
this.$dataValue = dataValue;
this.$dataValue.statusCode = this.$dataValue.statusCode || StatusCodes.Good;
}
// repair missing timestamps
const now = new Date();
if (!dataValue.serverTimestamp) {
this.$dataValue.serverTimestamp = old_dataValue.serverTimestamp;
this.$dataValue.serverPicoseconds = old_dataValue.serverPicoseconds;
this.$dataValue.serverTimestamp = old_dataValue.serverTimestamp || now;
this.$dataValue.serverPicoseconds = old_dataValue.serverPicoseconds || 0;
}
if (!dataValue.sourceTimestamp) {
this.$dataValue.sourceTimestamp = old_dataValue.sourceTimestamp;
this.$dataValue.sourcePicoseconds = old_dataValue.sourcePicoseconds;
this.$dataValue.sourceTimestamp = old_dataValue.sourceTimestamp || now;
this.$dataValue.sourcePicoseconds = old_dataValue.sourcePicoseconds || 0;
}

if (!sameDataValue(old_dataValue, dataValue)) {
this.emit("value_changed", this.$dataValue, indexRange);
if (this.getBasicDataType() === DataType.ExtensionObject) {
const preciseClock = coerceClock(this.$dataValue.sourceTimestamp, this.$dataValue.sourcePicoseconds);
const cache: Set<UAVariable> = new Set();
if (this.$$extensionObjectArray) {
this.touchValue(preciseClock);
propagateTouchValueDownwardArray(this, preciseClock, cache);
} else {
this.touchValue(preciseClock);
propagateTouchValueDownward(this, preciseClock, cache);
}
} else {
this.emit("value_changed", this.$dataValue, indexRange);
}
}
}

Expand Down Expand Up @@ -2067,7 +2103,7 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOpt
this._get_func = options.get;

const timestamped_get_func_from__Variable_bind_with_simple_get = () => {
const value = this._get_func();
const value: Variant | StatusCode = this._get_func();

/* istanbul ignore next */
if (!is_Variant_or_StatusCode(value)) {
Expand All @@ -2081,10 +2117,10 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOpt
);
}
if (is_StatusCode(value)) {
return new DataValue({ statusCode: value });
return new DataValue({ statusCode: value as StatusCode });
} else {
if (!this.$dataValue || !this.$dataValue.statusCode.isGoodish() || !sameVariant(this.$dataValue.value, value)) {
this.setValueFromSource(value, StatusCodes.Good);
if (!this.$dataValue || !this.$dataValue.statusCode.isGoodish() || !sameVariant(this.$dataValue.value, value as Variant)) {
this._inner_replace_dataValue(new DataValue({ value }));
}
return this.$dataValue;
}
Expand Down
72 changes: 56 additions & 16 deletions packages/node-opcua-address-space/src/ua_variable_impl_ext_obj.ts
Expand Up @@ -104,12 +104,12 @@ export function _touchValue(property: UAVariableImpl, now: PreciseClock): void {
property.$dataValue.serverTimestamp = now.timestamp;
property.$dataValue.serverPicoseconds = now.picoseconds;
property.$dataValue.statusCode = StatusCodes.Good;
if (property.minimumSamplingInterval === 0) {
if (property.listenerCount("value_changed") > 0) {
const clonedDataValue = property.readValue();
property.emit("value_changed", clonedDataValue);
}
// if (property.minimumSamplingInterval === 0) {
if (property.listenerCount("value_changed") > 0) {
const clonedDataValue = property.readValue();
property.emit("value_changed", clonedDataValue);
}
// }
}

export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
Expand All @@ -126,7 +126,7 @@ export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseCloc
}
}

function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
export function propagateTouchValueDownward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
if (!self.isExtensionObject()) return;
// also propagate changes to embeded variables
const dataTypeNode = self.getDataTypeNode();
Expand Down Expand Up @@ -336,7 +336,7 @@ function _installFields2(uaVariable: UAVariableImpl, { get, set }: {
propertyNode.$dataValue.serverPicoseconds = uaVariable.$dataValue.serverPicoseconds;
propertyNode.$dataValue.value.dataType = propertyNode.dataTypeObj.basicDataType;
propertyNode.$dataValue.value.arrayType = propertyNode.valueRank === -1 ? VariantArrayType.Scalar : (propertyNode.valueRank === 1 ? VariantArrayType.Array : VariantArrayType.Matrix)
propertyNode.$dataValue.value.dimensions =propertyNode.valueRank > 1 ? propertyNode.arrayDimensions : null;
propertyNode.$dataValue.value.dimensions = propertyNode.valueRank > 1 ? propertyNode.arrayDimensions : null;

const fieldName = field.name!;
installDataValueGetter(propertyNode, () => get(fieldName));
Expand Down Expand Up @@ -398,7 +398,7 @@ function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolea
function _innerBindExtensionObjectScalar(uaVariable: UAVariableImpl,
{ get, set }: {
get: () => ExtensionObject;
set: (value: ExtensionObject, sourceTimestamp: PreciseClock) => void;
set: (value: ExtensionObject, sourceTimestamp: PreciseClock, cache: Set<UAVariableImpl>) => void;
},
options?: BindExtensionObjectOptions
) {
Expand All @@ -417,7 +417,8 @@ function _innerBindExtensionObjectScalar(uaVariable: UAVariableImpl,
/** */
const ext = dataValue.value.value;
const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
set(ext, sourceTime);
const cache = new Set<UAVariableImpl>();
set(ext, sourceTime, cache);
}

_installFields2(uaVariable, {
Expand Down Expand Up @@ -607,6 +608,21 @@ export function _bindExtensionObjectArrayOrMatrix(
uaVariable.$dataValue.value.dataType = DataType.ExtensionObject;
uaVariable.$dataValue.value.value = uaVariable.$$extensionObjectArray;


// make sure uaVariable.$dataValue cannot be inadvertantly changed from this point onward
const $dataValue = uaVariable.$dataValue;
Object.defineProperty(uaVariable, "$dataValue", {
get(): DataValue {
return $dataValue;
},
set() {
throw new Error("$dataValue is now sealed , you should not change internal $dataValue!");
},
// writable: true,
enumerable: true,
configurable: true,
});

uaVariable.bindVariable({
get: () => uaVariable.$dataValue.value
}, true);
Expand Down Expand Up @@ -634,7 +650,6 @@ export function _bindExtensionObjectArrayOrMatrix(
}) as UAVariableImpl;
}

uaElement.$dataValue.value.dataType = DataType.ExtensionObject;
uaElement.$dataValue.statusCode = StatusCodes.Good;
uaElement.$dataValue.sourceTimestamp = uaVariable.$dataValue.sourceTimestamp;
uaElement.$dataValue.sourcePicoseconds = uaVariable.$dataValue.sourcePicoseconds;
Expand All @@ -645,16 +660,21 @@ export function _bindExtensionObjectArrayOrMatrix(

{
const capturedIndex = i;
const capturedUaElement = uaElement as UAVariableImpl;
_innerBindExtensionObjectScalar(uaElement,
{
get: () => uaVariable.$$extensionObjectArray[capturedIndex],
set: (newValue: ExtensionObject, sourceTimestamp: PreciseClock) => {
uaVariable.$$extensionObjectArray[capturedIndex] = newValue;
uaVariable.touchValue();
propagateTouchValueDownward(uaVariable, sourceTimestamp);
propagateTouchValueUpward(uaVariable, sourceTimestamp);
},
set: (newValue: ExtensionObject, sourceTimestamp: PreciseClock, cache: Set<UAVariableImpl>) => {

assert(!isProxy(uaVariable.$$extensionObjectArray[capturedIndex]));
uaVariable.$$extensionObjectArray[capturedIndex] = newValue;
if (uaVariable.$$extensionObjectArray !== uaVariable.$dataValue.value.value) {
console.log("uaVariable", uaVariable.nodeId.toString());
console.log("Houston! We have a problem ");
}
propagateTouchValueDownward(capturedUaElement, sourceTimestamp, cache);
propagateTouchValueUpward(capturedUaElement, sourceTimestamp, cache);
}
}, { ...options, force: true });

}
Expand Down Expand Up @@ -717,3 +737,23 @@ export function extractPartialData(path: string | string[], extensionObject: Ext
c1[name] = c2[name];
return partialData;
}

export function propagateTouchValueDownwardArray(uaVariable: UAVariableImpl, now: PreciseClock, cache: Set<UAVariable>) {

if (!uaVariable.$$extensionObjectArray) return;
const arrayDimensions = uaVariable.arrayDimensions || [];
const totalLength = uaVariable.$$extensionObjectArray.length;

const indexIterator = new IndexIterator(arrayDimensions);
for (let i = 0; i < totalLength; i++) {

const index = indexIterator.next();

const { browseName, nodeId } = composeBrowseNameAndNodeId(uaVariable, index);
const uaElement = uaVariable.getComponentByName(browseName) as UAVariableImpl | null;
if (uaElement?.nodeClass === NodeClass.Variable) {
uaElement.touchValue(now);
propagateTouchValueDownward(uaElement, now, cache);
}
}
}

0 comments on commit ec021df

Please sign in to comment.