Skip to content

Commit

Permalink
fix writing range on matrix
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Mar 2, 2021
1 parent b5d206f commit 0bad27c
Show file tree
Hide file tree
Showing 4 changed files with 353 additions and 23 deletions.
86 changes: 63 additions & 23 deletions packages/node-opcua-address-space/src/ua_variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from "node-opcua-data-model";
import { extractRange, sameDataValue, DataValue, DataValueLike } from "node-opcua-data-value";
import { coerceClock, getCurrentClock, PreciseClock } from "node-opcua-date-time";
import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
import { ExtensionObject } from "node-opcua-extension-object";
import { NodeId } from "node-opcua-nodeid";
import { NumericRange } from "node-opcua-numeric-range";
Expand Down Expand Up @@ -67,6 +67,7 @@ import { SessionContext } from "./session_context";
import { EnumerationInfo, IEnumItem, UADataType } from "./ua_data_type";

const debugLog = make_debugLog(__filename);
const warningLog = make_warningLog(__filename);
const doDebug = checkDebugFlag(__filename);
const errorLog = make_errorLog(__filename);

Expand Down Expand Up @@ -138,14 +139,22 @@ function _dataType_toUADataType(addressSpace: AddressSpace, dataType: DataType):
* @param nodeId
* @return {boolean} true if the variant dataType is compatible with the Variable DataType
*/
function validateDataType(addressSpace: AddressSpace, dataTypeNodeId: NodeId, variantDataType: DataType, nodeId: NodeId): boolean {
function validateDataType(
addressSpace: AddressSpace,
dataTypeNodeId: NodeId,
variantDataType: DataType,
nodeId: NodeId,
allowNulls: boolean
): boolean {
if (variantDataType === DataType.ExtensionObject) {
return true;
}
if (variantDataType === DataType.Null) {
if (variantDataType === DataType.Null && allowNulls) {
return true;
}

if (variantDataType === DataType.Null && !allowNulls) {
return false;
}
let builtInType: string;
let builtInUADataType: UADataTypePublic;

Expand Down Expand Up @@ -735,27 +744,51 @@ export class UAVariable extends BaseNode implements UAVariablePublic {
return callback!(null, StatusCodes.BadIndexRangeInvalid);
}

const newArr = correctedDataValue.value.value;
// check that source data is an array
if (correctedDataValue.value.arrayType !== VariantArrayType.Array) {
const newArrayOrMatrix = correctedDataValue.value.value;

if (correctedDataValue.value.arrayType === VariantArrayType.Array) {
if (this._dataValue.value.arrayType !== VariantArrayType.Array) {
return callback(null, StatusCodes.BadTypeMismatch);
}
// check that destination data is also an array
assert(check_valid_array(this._dataValue.value.dataType, this._dataValue.value.value));
const destArr = this._dataValue.value.value;
const result = indexRange.set_values(destArr, newArrayOrMatrix);

if (result.statusCode.isNot(StatusCodes.Good)) {
return callback!(null, result.statusCode);
}
correctedDataValue.value.value = result.array;

// scrap original array so we detect range
this._dataValue.value.value = null;
} else if (correctedDataValue.value.arrayType === VariantArrayType.Matrix) {
const dimensions = this._dataValue.value.dimensions;
if (this._dataValue.value.arrayType !== VariantArrayType.Matrix || !dimensions) {
// not a matrix !
return callback!(null, StatusCodes.BadTypeMismatch);
}
const matrix = this._dataValue.value.value;
const result = indexRange.set_values_matrix(
{
matrix,
dimensions
},
newArrayOrMatrix
);
if (result.statusCode.isNot(StatusCodes.Good)) {
return callback!(null, result.statusCode);
}
correctedDataValue.value.dimensions = this._dataValue.value.dimensions;
correctedDataValue.value.value = result.matrix;

// scrap original array so we detect range
this._dataValue.value.value = null;
} else {
return callback!(null, StatusCodes.BadTypeMismatch);
}

// check that destination data is also an array
assert(check_valid_array(this._dataValue.value.dataType, this._dataValue.value.value));
const destArr = this._dataValue.value.value;
const result = indexRange.set_values(destArr, newArr);

if (result.statusCode.isNot(StatusCodes.Good)) {
return callback!(null, result.statusCode);
}
correctedDataValue.value.value = result.array;

// scrap original array so we detect range
this._dataValue.value.value = null;
}
this._internal_set_dataValue(correctedDataValue, indexRange);
// xx this._dataValue = correctedDataValue;
}
callback!(err, statusCode1);
}
Expand All @@ -767,6 +800,7 @@ export class UAVariable extends BaseNode implements UAVariablePublic {
writeValueOptions: WriteValueOptions | WriteValue,
callback?: (err: Error | null, statusCode?: StatusCode) => void
): any {
// istanbul ignore next
if (!callback) {
throw new Error("Internal error");
}
Expand Down Expand Up @@ -1260,7 +1294,7 @@ export class UAVariable extends BaseNode implements UAVariablePublic {
value: prepareVariantValue(dataType, this.$extensionObject[name])
}));
*/

assert(propertyNode.readValue().statusCode.equals(StatusCodes.Good));

const self = this;
Expand Down Expand Up @@ -1588,14 +1622,20 @@ export class UAVariable extends BaseNode implements UAVariablePublic {
}

public _validate_DataType(variantDataType: DataType): boolean {
return validateDataType(this.addressSpace, this.dataType, variantDataType, this.nodeId);
return validateDataType(this.addressSpace, this.dataType, variantDataType, this.nodeId, /* allow Nulls */ false);
}

public _internal_set_dataValue(dataValue: DataValue, indexRange?: NumericRange | null) {
assert(dataValue, "expecting a dataValue");
assert(dataValue instanceof DataValue, "expecting dataValue to be a DataValue");
assert(dataValue !== this._dataValue, "expecting dataValue to be different from previous DataValue instance");

// istanbul ignore next
if (dataValue.value.arrayType === VariantArrayType.Matrix) {
if (dataValue.value.value.length !== 0 && dataValue.value.value.length !== dataValue.value.dimensions![0] * dataValue.value.dimensions![1]) {
warningLog("Internal Error: matrix dimension doesn't match : ", dataValue.toString());
}
}
if (dataValue.value.dataType === DataType.ExtensionObject) {
if (!this.checkExtensionObjectIsCorrect(dataValue.value.value)) {
console.log(dataValue.toString());
Expand Down
247 changes: 247 additions & 0 deletions packages/node-opcua-address-space/test/test_multi_dimensional_array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import "should";
import { DataValue } from "node-opcua-data-value";
import { NumericRange } from "node-opcua-numeric-range";
import { StatusCodes } from "node-opcua-status-code";
import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
import { AddressSpace, SessionContext, UAVariable } from "..";
import { getMiniAddressSpace } from "../testHelpers";

describe("Multi-Dimensional Array", () => {
let addressSpace: AddressSpace;
let matrixVariable: UAVariable;
let arrayVariable: UAVariable;
const defaultMatrixValue = new Variant({
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [4, 5],
// prettier-ignore
value: new Float64Array([
11, 12, 13, 14, 15,
21, 22, 23, 24, 25,
31, 32, 33, 34, 35,
41, 42, 43, 44, 45,
])
});
const defaultArrayValue = new Variant({
dataType: DataType.Double,
arrayType: VariantArrayType.Array,
// prettier-ignore
value: new Float64Array([
11, 12, 13, 14, 15,
])
});

before(async () => {
addressSpace = await getMiniAddressSpace();
const namespace1 = addressSpace.getOwnNamespace();

matrixVariable = namespace1.addVariable({
browseName: "MatrixVariable",
dataType: "Double",
valueRank: 2,
arrayDimensions: defaultMatrixValue.dimensions,
value: defaultMatrixValue
});
matrixVariable.arrayDimensions.should.eql([4, 5]);
const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);

arrayVariable = namespace1.addVariable({
browseName: "ArrayVariable",
dataType: "Double",
valueRank: 1,
value: defaultArrayValue
});
});
after(() => {
addressSpace.dispose();
});
beforeEach(async () => {
await matrixVariable.writeValue(SessionContext.defaultContext, new DataValue({ value: defaultMatrixValue }));
await arrayVariable.writeValue(SessionContext.defaultContext, new DataValue({ value: defaultArrayValue }));
});
it("MDA-1 should write a single element in a matrix", async () => {
const statusCode = await matrixVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [1, 1],
value: new Float64Array([8888])
}
}),
new NumericRange([2, 2], [3, 3])
);
statusCode.should.eql(StatusCodes.Good);

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
21, 22, 23, 24, 25,
31, 32, 33, 8888, 35,
41, 42, 43, 44, 45,
])
);
});
it("MDA-2 should write a sub-matrix element in a matrix", async () => {
const statusCode = await matrixVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [2, 3],
// prettier-ignore
value: new Float64Array([
8888, 8889, 8890,
7777, 7778, 7779
])
}
}),
"1:2,2:4"
);
statusCode.should.eql(StatusCodes.Good);

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
21, 22, 8888, 8889, 8890,
31, 32, 7777, 7778, 7779,
41, 42, 43, 44, 45,
])
);
});

it("MDA-3 write should return an error if the sub-matrix element is written outside the boundary", async () => {
const statusCode = await matrixVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [2, 3],
// prettier-ignore
value: new Float64Array([
8888, 8889, 8890,
7777, 7778, 7779
])
}
}),
"3:4,2:4" // Wrong rows!
);
statusCode.should.eql(StatusCodes.BadTypeMismatch);

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
21, 22, 23, 24, 25,
31, 32, 33, 34, 35,
41, 42, 43, 44, 45,
])
);
});
it("MDA-4 write should return an OK if the sub-matrix element is written inside the boundary", async () => {
const statusCode = await matrixVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [2, 3],
// prettier-ignore
value: new Float64Array([
8888, 8889, 8890,
7777, 7778, 7779
])
}
}),
"2:3,2:4" // Correct rows! touching the sides
);
statusCode.should.eql(StatusCodes.Good);

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
21, 22, 23, 24, 25,
31, 32, 8888, 8889, 8890,
41, 42, 7777, 7778, 7779,
])
);
});
it("MDA-5 write should return BadTypeMismatch on an attempt to write a sub array on a matrix", async () => {
const statusCode = await matrixVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Array,
// prettier-ignore
value: new Float64Array([
8888, 8889
])
}
}),
"1:2" // Wrong rows!
);

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
21, 22, 23, 24, 25,
31, 32, 33, 34, 35,
41, 42, 43, 44, 45,
])
);
statusCode.should.eql(StatusCodes.BadTypeMismatch);
});
it("MDA-6 write should return BadTypeMismatch on an attempt to write a sub matrix on a array", async () => {
const statusCode = await arrayVariable.writeValue(
SessionContext.defaultContext,
new DataValue({
value: {
dataType: DataType.Double,
arrayType: VariantArrayType.Matrix,
dimensions: [2, 3],
// prettier-ignore
value: new Float64Array([
8888, 8889, 8890,
7777, 7778, 7779
])
}
}),
"0:1,0:2"
);

const dataValueCheck = arrayVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Array);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
11, 12, 13, 14, 15,
])
);
statusCode.should.eql(StatusCodes.BadTypeMismatch);
});
});

0 comments on commit 0bad27c

Please sign in to comment.