Skip to content

Commit

Permalink
Fix extension array/matrix binding access to individual props
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Mar 12, 2023
1 parent 121ed7a commit 00be152
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 64 deletions.
6 changes: 5 additions & 1 deletion packages/node-opcua-address-space/src/ua_variable_impl.ts
Expand Up @@ -539,6 +539,10 @@
return this.addressSpacePrivate.isEnumeration(this.dataType);
}

/**
* return true if the DataType is of type Extension object
* this is not taking into account the valueRank of the variable
*/
public isExtensionObject(): boolean {
// DataType must be one of Structure
if (this.dataType.isEmpty()) return false;
Expand Down Expand Up @@ -1802,7 +1806,7 @@
propagateTouchValueDownward(this, preciseClock, cache);
}
} else {
this.emit("value_changed", this.$dataValue, indexRange);
this.emit("value_changed", this.$dataValue.clone(), indexRange);
}
}
}
Expand Down
40 changes: 25 additions & 15 deletions packages/node-opcua-address-space/src/ua_variable_impl_ext_obj.ts
Expand Up @@ -104,12 +104,9 @@ 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);
property.emit("value_changed", property.$dataValue.clone());
}
// }
}

export function propagateTouchValueUpward(self: UAVariableImpl, now: PreciseClock, cache?: Set<UAVariable>): void {
Expand Down Expand Up @@ -346,6 +343,7 @@ function _installFields2(uaVariable: UAVariableImpl, { get, set }: {
const sourceTime = coerceClock(dataValue.sourceTimestamp, dataValue.sourcePicoseconds);
const value = dataValue.value.value;
set(field.name!, value, sourceTime);
propertyNode.touchValue(sourceTime);
}

if (propertyNode.dataTypeObj.basicDataType === DataType.ExtensionObject) {
Expand Down Expand Up @@ -396,9 +394,10 @@ function isVariableContainingExtensionObject(uaVariable: UAVariableImpl): boolea
}

function _innerBindExtensionObjectScalar(uaVariable: UAVariableImpl,
{ get, set }: {
{ get, set, setField }: {
get: () => ExtensionObject;
set: (value: ExtensionObject, sourceTimestamp: PreciseClock, cache: Set<UAVariableImpl>) => void;
setField: (fieldName: string, value: any, sourceTimestamp: PreciseClock, cache?: Set<UAVariableImpl>) => void;
},
options?: BindExtensionObjectOptions
) {
Expand All @@ -420,10 +419,7 @@ function _innerBindExtensionObjectScalar(uaVariable: UAVariableImpl,
return extObj[lowerFirstLetter(fieldName)];
},
set: (fieldName: string, value: any, sourceTime: PreciseClock) => {
const extObj = get() as any;
extObj[lowerFirstLetter(fieldName)] = value;
//1 propagateTouchValueDownward(uaVariable, sourceTime);
//1 propagateTouchValueUpward(uaVariable, sourceTime);
setField(fieldName, value, sourceTime);
}
}, options);

Expand Down Expand Up @@ -530,7 +526,12 @@ export function _bindExtensionObject(
_innerBindExtensionObjectScalar(uaVariable,
{
get: () => uaVariable.$extensionObject,
set: (value: ExtensionObject) => installExt(uaVariable, value)
set: (value: ExtensionObject) => installExt(uaVariable, value),
setField: (fieldName: string, value: any) => {
const extObj = uaVariable.$extensionObject;
getProxyTarget(extObj)[lowerFirstLetter(fieldName)] = value;
}

}, options);
return;
} else if (uaVariable.valueRank === 1 /** Array */) {
Expand All @@ -546,7 +547,11 @@ export function _bindExtensionObject(
_innerBindExtensionObjectScalar(uaVariable,
{
get: () => uaVariable.$extensionObject,
set: (value: ExtensionObject) => installExt(uaVariable, value)
set: (value: ExtensionObject) => installExt(uaVariable, value),
setField: (fieldName: string, value: any) => {
const extObj = uaVariable.$extensionObject;
getProxyTarget(extObj)[lowerFirstLetter(fieldName)] = value;
}
}, options);
}
}
Expand Down Expand Up @@ -575,7 +580,7 @@ export function _bindExtensionObjectArrayOrMatrix(
options?: BindExtensionObjectOptions
): ExtensionObject[] {

options = options || { createMissingProp: false};
options = options || { createMissingProp: false };
options.createMissingProp = options.createMissingProp || false;

// istanbul ignore next
Expand All @@ -592,7 +597,7 @@ export function _bindExtensionObjectArrayOrMatrix(
assert(Array.isArray(uaVariable.$dataValue.value.value));
optionalExtensionObjectArray = uaVariable.$dataValue.value.value;
}

if ((arrayDimensions.length === 0 || arrayDimensions.length === 1 && arrayDimensions[0] === 0) && optionalExtensionObjectArray) {
arrayDimensions[0] = optionalExtensionObjectArray.length;
}
Expand Down Expand Up @@ -652,7 +657,7 @@ export function _bindExtensionObjectArrayOrMatrix(
if (!options.createMissingProp) {
continue;
}

uaElement = namespace.addVariable({
browseName,
nodeId,
Expand All @@ -678,7 +683,6 @@ export function _bindExtensionObjectArrayOrMatrix(
{
get: () => uaVariable.$$extensionObjectArray[capturedIndex],
set: (newValue: ExtensionObject, sourceTimestamp: PreciseClock, cache: Set<UAVariableImpl>) => {

assert(!isProxy(uaVariable.$$extensionObjectArray[capturedIndex]));
uaVariable.$$extensionObjectArray[capturedIndex] = newValue;
if (uaVariable.$$extensionObjectArray !== uaVariable.$dataValue.value.value) {
Expand All @@ -687,6 +691,12 @@ export function _bindExtensionObjectArrayOrMatrix(
}
propagateTouchValueDownward(capturedUaElement, sourceTimestamp, cache);
propagateTouchValueUpward(capturedUaElement, sourceTimestamp, cache);
},
setField: (fieldName: string, newValue: any, sourceTimestamp: PreciseClock, cache?: Set<UAVariableImpl>) => {
// istanbul ignore next doDebug && debugLog("setField", fieldName, newValue, sourceTimestamp, cache);
const extObj = uaVariable.$$extensionObjectArray[capturedIndex];
(isProxy(extObj) ? getProxyTarget(extObj) : extObj)[lowerFirstLetter(fieldName)] = newValue;
propagateTouchValueUpward(capturedUaElement, sourceTimestamp, cache);
}
}, { ...options, force: true });

Expand Down
Expand Up @@ -14,6 +14,8 @@ import { ExtensionObject } from "node-opcua-extension-object";
import { AddressSpace, BaseNode, INamespace, PseudoSession, UAVariable } from "..";
import { generateAddressSpace } from "../nodeJS";

const nextYear = (new Date()).getUTCFullYear()+1;

async function simulateExternalWriteEx(node: BaseNode, value: ExtensionObject, sourceTimestamp: DateTime) {
const addressSpace = node.addressSpace;
const session = new PseudoSession(addressSpace);
Expand Down Expand Up @@ -93,8 +95,8 @@ describe("Extending extension object variables", function () {
value: p1.clone(),
arrayType: VariantArrayType.Scalar,
dataType: DataType.ExtensionObject
}, StatusCodes.Good, new Date(Date.UTC(2022, 0, 1, 0, 0, 0)));
uaVariable.readValue().sourceTimestamp?.toISOString().should.eql("2022-01-01T00:00:00.000Z");
}, StatusCodes.Good, new Date(Date.UTC(nextYear, 0, 1, 0, 0, 0)));
uaVariable.readValue().sourceTimestamp?.toISOString().should.eql( `${nextYear}-01-01T00:00:00.000Z`);

uaVariable.installExtensionObjectVariables();
return uaVariable;
Expand Down Expand Up @@ -124,7 +126,7 @@ describe("Extending extension object variables", function () {
value: [p1.clone(), p2.clone()],
arrayType: VariantArrayType.Array,
dataType: DataType.ExtensionObject
}, StatusCodes.Good, new Date(Date.UTC(2022, 0, 1)));
}, StatusCodes.Good, new Date(Date.UTC(nextYear, 0, 1)));

uaVariable.installExtensionObjectVariables();
return uaVariable;
Expand Down Expand Up @@ -186,19 +188,20 @@ describe("Extending extension object variables", function () {
zDataValue.value.value.should.eql(z);

//s dataValue.sourceTimestamp?.toISOString().should.eql("2023-01-02T06:47:55.065Z");
xDataValue.sourceTimestamp?.getTime().should.lessThanOrEqual(dataValue.sourceTimestamp!.getTime());
xDataValue.sourceTimestamp?.getTime().should.lessThanOrEqual(dataValue.sourceTimestamp!.getTime(), "x " + xDataValue.sourceTimestamp.toISOString() + " ext =" + dataValue.sourceTimestamp?.toISOString());
yDataValue.sourceTimestamp?.getTime().should.lessThanOrEqual(dataValue.sourceTimestamp!.getTime());
zDataValue.sourceTimestamp?.getTime().should.lessThanOrEqual(dataValue.sourceTimestamp!.getTime());

}
verify({ x: 1, y: 2, z: 3 });

//
//
await simulateExternalWrite(uaZ, 33, new Date(Date.UTC(2022, 0, 2, 0, 0, 0)));
await simulateExternalWrite(uaZ, 33, new Date(Date.UTC(nextYear, 0, 2, 0, 0, 0)));
verify({ x: 1, y: 2, z: 33 });
//

await simulateExternalWriteEx(uaVariable, p2.clone(), new Date(Date.UTC(2022, 0, 3, 0, 0, 0)));
await simulateExternalWriteEx(uaVariable, p2.clone(), new Date(Date.UTC(nextYear, 0, 3, 0, 0, 0)));

verify({ x: 4, y: 5, z: 6 });

Expand Down Expand Up @@ -344,14 +347,15 @@ describe("Extending extension object variables", function () {
// now write a leaf property and verify that the value cascade upward
const x = el1.getComponentByName("X")!;


await simulateExternalWrite(x, 44, new Date(Date.UTC(2022, 0, 2)));
const nextYear = (new Date()).getUTCFullYear()+1;

await simulateExternalWrite(x, 44, new Date(Date.UTC(nextYear, 0, 2)));
verify([{ x: 1, y: 2, z: 3 }, { x: 44, y: 5, z: 6 }]);

await simulateExternalWriteEx(el1, p3.clone(), new Date(Date.UTC(2022, 0, 3)));
await simulateExternalWriteEx(el1, p3.clone(), new Date(Date.UTC(nextYear, 0, 3)));
verify([{ x: 1, y: 2, z: 3 }, { x: 7, y: 8, z: 9 }]);

await simulateExternalWrite(x, 22, new Date(Date.UTC(2022, 0, 4)));
await simulateExternalWrite(x, 22, new Date(Date.UTC(nextYear, 0, 4)));
verify([{ x: 1, y: 2, z: 3 }, { x: 22, y: 8, z: 9 }]);


Expand Down Expand Up @@ -506,8 +510,9 @@ describe("Extending extension object variables", function () {
]);



{
await simulateExternalWrite(el11.getComponentByName("X")!, 33, new Date(Date.UTC(2022, 0, 3, 0, 0, 0)));
await simulateExternalWrite(el11.getComponentByName("X")!, 33, new Date(Date.UTC(nextYear, 0, 3, 0, 0, 0)));


verify([
Expand All @@ -518,7 +523,7 @@ describe("Extending extension object variables", function () {
}
{

await simulateExternalWriteEx(el11, p4.clone(), new Date(Date.UTC(2022, 0, 4, 0, 0, 0)));
await simulateExternalWriteEx(el11, p4.clone(), new Date(Date.UTC(nextYear, 0, 4, 0, 0, 0)));

const dataValue = el11.readValue();
dataValue.value.dataType.should.eql(DataType.ExtensionObject);
Expand All @@ -534,7 +539,7 @@ describe("Extending extension object variables", function () {
// now change again some of the individual property
{

await simulateExternalWrite(el11.getComponentByName("X")!, 100, new Date(Date.UTC(2022, 0, 5, 0, 0, 0)));
await simulateExternalWrite(el11.getComponentByName("X")!, 100, new Date(Date.UTC(nextYear, 0, 5, 0, 0, 0)));

const dataValue = el11.readValue();
dataValue.value.dataType.should.eql(DataType.ExtensionObject);
Expand Down
Expand Up @@ -27,7 +27,7 @@ describe("testing extension object binding", function () {
organizedBy: addressSpace.rootFolder.objects
});
const cncPositionDataType = addressSpace.findDataType("CncPositionDataType", nsCNC)!;
const posTcpBcsX = channel.getComponentByName("PosTcpBcsX")! as UAVariable;
const posTcpBcsX = channel.getComponentByName("PosTcpBcsX")! as UAVariable;

posTcpBcsX.readValue().value.value.actPos.should.eql(0);
posTcpBcsX.readValue().value.value.cmdPos.should.eql(0);
Expand Down Expand Up @@ -74,7 +74,9 @@ describe("testing extension object binding", function () {
const remDist = posTcpBcsX.getComponentByName("RemDist")! as UAVariable;

const changeSpyActPos = sinon.spy();
actPos.on("value_changed", changeSpyActPos);
actPos.on("value_changed", ()=>{
changeSpyActPos()
});

const changeSpyCmdPos = sinon.spy();
cmdPos.on("value_changed", changeSpyCmdPos);
Expand Down

0 comments on commit 00be152

Please sign in to comment.