node-opcua-address-space 2.102.0 → 2.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
- "version": "2.102.0",
3
+ "version": "2.103.0",
4
4
  "description": "pure nodejs OPCUA SDK - module address-space",
5
5
  "main": "./dist/src/index_current.js",
6
6
  "types": "./dist/source/index.d.ts",
@@ -22,14 +22,14 @@
22
22
  "chalk": "4.1.2",
23
23
  "dequeue": "^1.0.5",
24
24
  "lodash": "4.17.21",
25
- "node-opcua-address-space-base": "2.102.0",
25
+ "node-opcua-address-space-base": "2.103.0",
26
26
  "node-opcua-assert": "2.98.1",
27
27
  "node-opcua-basic-types": "2.99.0",
28
28
  "node-opcua-binary-stream": "2.98.1",
29
- "node-opcua-client-dynamic-extension-object": "2.102.0",
29
+ "node-opcua-client-dynamic-extension-object": "2.103.0",
30
30
  "node-opcua-constants": "2.98.1",
31
31
  "node-opcua-crypto": "^2.1.2",
32
- "node-opcua-data-access": "2.102.0",
32
+ "node-opcua-data-access": "2.103.0",
33
33
  "node-opcua-data-model": "2.102.0",
34
34
  "node-opcua-data-value": "2.102.0",
35
35
  "node-opcua-date-time": "2.99.0",
@@ -38,20 +38,20 @@
38
38
  "node-opcua-extension-object": "2.99.0",
39
39
  "node-opcua-factory": "2.99.0",
40
40
  "node-opcua-nodeid": "2.99.0",
41
- "node-opcua-nodeset-ua": "2.102.0",
41
+ "node-opcua-nodeset-ua": "2.103.0",
42
42
  "node-opcua-numeric-range": "2.102.0",
43
43
  "node-opcua-object-registry": "2.99.0",
44
- "node-opcua-pseudo-session": "2.102.0",
45
- "node-opcua-service-browse": "2.102.0",
46
- "node-opcua-service-call": "2.102.0",
47
- "node-opcua-service-history": "2.102.0",
48
- "node-opcua-service-translate-browse-path": "2.102.0",
49
- "node-opcua-service-write": "2.102.0",
44
+ "node-opcua-pseudo-session": "2.103.0",
45
+ "node-opcua-service-browse": "2.103.0",
46
+ "node-opcua-service-call": "2.103.0",
47
+ "node-opcua-service-history": "2.103.0",
48
+ "node-opcua-service-translate-browse-path": "2.103.0",
49
+ "node-opcua-service-write": "2.103.0",
50
50
  "node-opcua-status-code": "2.98.1",
51
- "node-opcua-types": "2.102.0",
51
+ "node-opcua-types": "2.103.0",
52
52
  "node-opcua-utils": "2.98.1",
53
53
  "node-opcua-variant": "2.102.0",
54
- "node-opcua-xml2json": "2.99.0",
54
+ "node-opcua-xml2json": "2.103.0",
55
55
  "semver": "^7.3.8",
56
56
  "set-prototype-of": "^1.0.0",
57
57
  "thenify": "^3.3.1",
@@ -61,10 +61,10 @@
61
61
  "mocha": "^10.2.0",
62
62
  "node-opcua-benchmarker": "2.98.1",
63
63
  "node-opcua-leak-detector": "2.99.0",
64
- "node-opcua-nodesets": "2.99.0",
64
+ "node-opcua-nodesets": "2.103.0",
65
65
  "node-opcua-packet-analyzer": "2.102.0",
66
- "node-opcua-service-filter": "2.102.0",
67
- "node-opcua-test-fixtures": "2.102.0",
66
+ "node-opcua-service-filter": "2.103.0",
67
+ "node-opcua-test-fixtures": "2.103.0",
68
68
  "should": "^13.2.3",
69
69
  "sinon": "^15.0.3",
70
70
  "source-map-support": "^0.5.21"
@@ -84,7 +84,7 @@
84
84
  "internet of things"
85
85
  ],
86
86
  "homepage": "http://node-opcua.github.io/",
87
- "gitHead": "07dcdd8e8c7f2b55544c6e23023093e35674829c",
87
+ "gitHead": "e206cac2daf39bd07e5ac6cbd744f966bb54759e",
88
88
  "files": [
89
89
  "dist",
90
90
  "distHelpers",
@@ -1,4 +1,9 @@
1
- import { AddVariableOptionsWithoutValue, AddYArrayItemOptions, BindVariableOptions, UADataType } from "node-opcua-address-space-base";
1
+ import {
2
+ AddVariableOptionsWithoutValue,
3
+ AddYArrayItemOptions,
4
+ BindVariableOptions,
5
+ UADataType
6
+ } from "node-opcua-address-space-base";
2
7
  import { NodeIdLike } from "node-opcua-nodeid";
3
8
  import { UAAnalogItem, UADataItem } from "node-opcua-nodeset-ua";
4
9
  import { EUInformation, EUInformationOptions } from "node-opcua-types";
@@ -40,11 +45,31 @@ export interface AddAnalogDataItemOptions extends AddDataItemOptions {
40
45
  engineeringUnits?: EUInformationOptions | EUInformation;
41
46
  minimumSamplingInterval?: number;
42
47
  dataType?: string | NodeIdLike | UADataType;
48
+
49
+ /**
50
+ * the acceptValueOutOfRange property indicates whether the write operation will accept or reject a value which
51
+ * is out of range of the instrumentRange.
52
+ *
53
+ * **if true**: during a writeOperation by a client if the dataValue that is outside of the instrumentRange
54
+ * it will be recorded database and the statusCode will be set to BadOutOfRange, and
55
+ * the write operation will return Good. If the variable supports historizing, the value will be recorded in the history database.
56
+ * as well.
57
+ *
58
+ * **if false**: during a writeOperation by a client, if the dataValue that is outside of the instrumentRange
59
+ * it will be denied and the write operation will return BadOutOfRange.
60
+ *
61
+ * @default undefined (false)
62
+ *
63
+ */
64
+ acceptValueOutOfRange?: boolean;
43
65
  }
44
66
 
67
+ export interface UAAnalogItemEx<T, DT extends DataType> extends UAAnalogItem<T, DT> {
68
+ acceptValueOutOfRange?: boolean;
69
+ }
45
70
  export interface INamespaceDataAccess {
46
71
  addDataItem<T, DT extends DataType>(options: AddDataItemOptions): UADataItem<T, DT>;
47
- addAnalogDataItem<T, DT extends DataType>(options: AddAnalogDataItemOptions): UAAnalogItem<T, DT>;
72
+ addAnalogDataItem<T, DT extends DataType>(options: AddAnalogDataItemOptions): UAAnalogItemEx<T, DT>;
48
73
 
49
74
  addYArrayItem<DT extends DataType.Double | DataType.Float>(options: AddYArrayItemOptions): UAYArrayItemEx<DT>;
50
75
 
@@ -352,11 +352,19 @@ export class PseudoSession implements IBasicSession {
352
352
  if (!obj) {
353
353
  return StatusCodes.BadNodeIdUnknown;
354
354
  }
355
- return promisify(obj.writeAttribute).call(obj, context, nodeToWrite);
356
- });
357
- Promise.all(statusCodesPromises).then((statusCodes) => {
358
- callback!(null, isArray ? statusCodes : statusCodes[0]);
355
+ try {
356
+ return promisify(obj.writeAttribute).call(obj, context, nodeToWrite);
357
+ } catch (err) {
358
+ return StatusCodes.BadInternalError;
359
+ }
359
360
  });
361
+ Promise.all(statusCodesPromises)
362
+ .then((statusCodes) => {
363
+ callback!(null, isArray ? statusCodes : statusCodes[0]);
364
+ })
365
+ .catch((err) => {
366
+ callback!(err);
367
+ });
360
368
  });
361
369
  }
362
370
  }
@@ -305,6 +305,7 @@ export class SessionContext implements ISessionContext {
305
305
  return true;
306
306
  }
307
307
  }
308
+ if (!this.session ) { return false; }
308
309
  const securityMode = this.session?.channel?.securityMode;
309
310
  if (accessRestrictions & AccessRestrictionsFlag.SigningRequired) {
310
311
  if (securityMode !== MessageSecurityMode.Sign && securityMode !== MessageSecurityMode.SignAndEncrypt) {
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @module node-opcua-address-space.DataAccess
3
+ */
4
+ import { StatusCodes } from "node-opcua-status-code";
5
+ import { StatusCode } from "node-opcua-status-code";
6
+ import { Range } from "node-opcua-types";
7
+ import { UAVariable } from "node-opcua-address-space-base";
8
+ import { NodeClass } from "node-opcua-data-model";
9
+ import { DataValue } from "node-opcua-data-value";
10
+ import { Variant } from "node-opcua-variant";
11
+ import { UAVariableImpl } from "../ua_variable_impl";
12
+
13
+ function validate_value_range(range: Range, variant: Variant) {
14
+ if (variant.value < range.low || variant.value > range.high) {
15
+ return false;
16
+ }
17
+ return true;
18
+ }
19
+
20
+ export function adjustDataValueStatusCode(
21
+ variable: UAVariableImpl,
22
+ dataValue: DataValue,
23
+ acceptValueOutOfRange: boolean
24
+ ): StatusCode {
25
+ const instrumentRange = variable.getChildByName("InstrumentRange") as UAVariable | null;
26
+ if (instrumentRange && instrumentRange.nodeClass === NodeClass.Variable) {
27
+ if (!validate_value_range(instrumentRange.readValue().value.value as Range, dataValue.value)) {
28
+ if (!acceptValueOutOfRange) {
29
+ return StatusCodes.BadOutOfRange;
30
+ } else {
31
+ dataValue.statusCode = StatusCodes.BadOutOfRange;
32
+ }
33
+ }
34
+ }
35
+ return StatusCodes.Good;
36
+ }
@@ -43,7 +43,7 @@ export { adjustNamespaceArray } from "./nodeset_tools/adjust_namespace_array";
43
43
  export { makeAttributeEventName } from "./base_node_impl";
44
44
  export { resolveReferenceNode, resolveReferenceType } from "./reference_impl";
45
45
 
46
- export * from "./data_access/check_variant_compatibility_ua_analog_item";
46
+ export * from "./data_access/adjust_datavalue_status_code";
47
47
  export * from "./data_access/add_dataItem_stuff";
48
48
  export * from "./data_access/ua_multistate_discrete_impl";
49
49
  export * from "./data_access/ua_multistate_value_discrete_impl";
@@ -76,7 +76,7 @@ import {
76
76
  UATwoStateDiscreteEx,
77
77
  UAYArrayItemEx
78
78
  } from "../source";
79
- import { AddAnalogDataItemOptions, AddDataItemOptions } from "../source/namespace_data_access";
79
+ import { AddAnalogDataItemOptions, AddDataItemOptions, UAAnalogItemEx } from "../source/namespace_data_access";
80
80
  import { UATwoStateVariableEx } from "../source/ua_two_state_variable_ex";
81
81
  import { UAMultiStateValueDiscreteEx } from "../source/interfaces/data_access/ua_multistate_value_discrete_ex";
82
82
  import { UAAlarmConditionEx } from "../source/interfaces/alarms_and_conditions/ua_alarm_condition_ex";
@@ -125,7 +125,6 @@ import { UAViewImpl } from "./ua_view_impl";
125
125
  import { UAStateMachineImpl, UATransitionImpl } from "./state_machine/finite_state_machine";
126
126
  import { _addMultiStateValueDiscrete } from "./data_access/ua_multistate_value_discrete_impl";
127
127
 
128
-
129
128
  function _makeHashKey(nodeId: NodeId): string | number {
130
129
  switch (nodeId.identifierType) {
131
130
  case NodeIdType.STRING:
@@ -931,6 +930,7 @@ export class NamespaceImpl implements NamespacePrivate {
931
930
 
932
931
  instrumentRange.on("value_changed", handler);
933
932
  }
933
+ (variable as any).acceptValueOutOfRange = options.acceptValueOutOfRange;
934
934
 
935
935
  if (Object.prototype.hasOwnProperty.call(options, "engineeringUnits")) {
936
936
  const engineeringUnits = new EUInformation(options.engineeringUnits);
@@ -1445,7 +1445,7 @@ export class NamespaceImpl implements NamespacePrivate {
1445
1445
  const _component = component as UAStateMachineImpl;
1446
1446
 
1447
1447
  assert(_component.nodeClass === NodeClass.Object || _component.nodeClass === NodeClass.ObjectType);
1448
-
1448
+
1449
1449
  const initialStateType = addressSpace.findObjectType("InitialStateType")!;
1450
1450
  const stateType = addressSpace.findObjectType("StateType")!;
1451
1451
 
@@ -1662,24 +1662,24 @@ export class NamespaceImpl implements NamespacePrivate {
1662
1662
 
1663
1663
  // istanbul ignore next
1664
1664
  if (this._nodeid_index.has(hashKey)) {
1665
-
1666
1665
  const exstingNode = this.findNode(node.nodeId)!;
1667
1666
  throw new Error(
1668
1667
  "node " +
1669
- node.browseName.toString() +
1670
- " nodeId = " +
1671
- node.nodeId.displayText() +
1672
- " already registered " +
1673
- node.nodeId.toString() +
1674
- "\n" +
1675
- " in namespace " +
1676
- this.namespaceUri +
1677
- " index = " +
1678
- this.index +
1679
- "\n" +
1680
- "existing node = " +
1681
- exstingNode.toString() +
1682
- "this parent : " + node.parentNodeId?.toString()
1668
+ node.browseName.toString() +
1669
+ " nodeId = " +
1670
+ node.nodeId.displayText() +
1671
+ " already registered " +
1672
+ node.nodeId.toString() +
1673
+ "\n" +
1674
+ " in namespace " +
1675
+ this.namespaceUri +
1676
+ " index = " +
1677
+ this.index +
1678
+ "\n" +
1679
+ "existing node = " +
1680
+ exstingNode.toString() +
1681
+ "this parent : " +
1682
+ node.parentNodeId?.toString()
1683
1683
  );
1684
1684
  }
1685
1685
 
@@ -1742,15 +1742,15 @@ export class NamespaceImpl implements NamespacePrivate {
1742
1742
  errorLog(
1743
1743
  chalk.red.bold(
1744
1744
  "Error: namespace index used at the front of the browseName " +
1745
- indexVerif +
1746
- " do not match the index of the current namespace (" +
1747
- this.index +
1748
- ")"
1745
+ indexVerif +
1746
+ " do not match the index of the current namespace (" +
1747
+ this.index +
1748
+ ")"
1749
1749
  )
1750
1750
  );
1751
1751
  errorLog(
1752
1752
  " Please fix your code so that the created node is inserted in the correct namespace," +
1753
- " please refer to the NodeOPCUA documentation"
1753
+ " please refer to the NodeOPCUA documentation"
1754
1754
  );
1755
1755
  }
1756
1756
  }
@@ -1990,13 +1990,16 @@ export class NamespaceImpl implements NamespacePrivate {
1990
1990
 
1991
1991
  // -----------------------------------------------------
1992
1992
  const hasGetter = (options: AddVariableOptions2) => {
1993
- return typeof options.value?.get === "function" || typeof options.value?.timestamped_get === "function"
1994
- }
1993
+ return typeof options.value?.get === "function" || typeof options.value?.timestamped_get === "function";
1994
+ };
1995
1995
 
1996
1996
  // istanbul ignore next
1997
1997
  if (options.minimumSamplingInterval === undefined && hasGetter(options)) {
1998
1998
  // a getter has been specified and no options.minimumSamplingInterval has been specified
1999
- warningLog("[NODE-OPCUA-W30", "namespace#addVariable a getter has been specified and minimumSamplingInterval is missing.\nMinimumSamplingInterval has been adjusted to 1000 ms");
1999
+ warningLog(
2000
+ "[NODE-OPCUA-W30",
2001
+ "namespace#addVariable a getter has been specified and minimumSamplingInterval is missing.\nMinimumSamplingInterval has been adjusted to 1000 ms"
2002
+ );
2000
2003
  options.minimumSamplingInterval = 1000;
2001
2004
  }
2002
2005
 
@@ -2004,7 +2007,10 @@ export class NamespaceImpl implements NamespacePrivate {
2004
2007
 
2005
2008
  // istanbul ignore next
2006
2009
  if (options.minimumSamplingInterval === 0 && hasGetter(options)) {
2007
- warningLog("[NODE-OPCUA-W31", "namespace#addVariable a getter has been specified and minimumSamplingInterval is 0.\nThis may conduct to an unpredicable behavior.\nPlease specify a non zero minimum sampling interval")
2010
+ warningLog(
2011
+ "[NODE-OPCUA-W31",
2012
+ "namespace#addVariable a getter has been specified and minimumSamplingInterval is 0.\nThis may conduct to an unpredicable behavior.\nPlease specify a non zero minimum sampling interval"
2013
+ );
2008
2014
  }
2009
2015
 
2010
2016
  let references = options.references || ([] as AddReferenceOpts[]);
@@ -2209,15 +2215,15 @@ export function isNonEmptyQualifiedName(browseName?: null | string | QualifiedNa
2209
2215
  return browseName.name!.length > 0;
2210
2216
  }
2211
2217
 
2212
- function _create_node_version_if_needed(node: BaseNode, options: {nodeVersion: boolean}) {
2218
+ function _create_node_version_if_needed(node: BaseNode, options: { nodeVersion: boolean }) {
2213
2219
  assert(options);
2214
2220
  if (options.nodeVersion) {
2215
2221
  assert(node.nodeClass === NodeClass.Variable || node.nodeClass === NodeClass.Object);
2216
2222
  // istanbul ignore next
2217
- if (node.getChildByName("NodeVersion")) {
2223
+ if (node.getChildByName("NodeVersion")) {
2218
2224
  return; // already exists
2219
2225
  }
2220
- const namespace = node.addressSpace.getOwnNamespace();
2226
+ const namespace = node.addressSpace.getOwnNamespace();
2221
2227
  const nodeVersion = namespace.addVariable({
2222
2228
  browseName: "NodeVersion",
2223
2229
  dataType: "String",
@@ -96,6 +96,7 @@ import {
96
96
  _installExtensionObjectBindingOnProperties,
97
97
  _touchValue
98
98
  } from "./ua_variable_impl_ext_obj";
99
+ import { adjustDataValueStatusCode } from "./data_access/adjust_datavalue_status_code";
99
100
 
100
101
  const debugLog = make_debugLog(__filename);
101
102
  const warningLog = make_warningLog(__filename);
@@ -849,7 +850,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
849
850
  // ----------------------------------
850
851
  if (this.$extensionObject || this.$$extensionObjectArray) {
851
852
  // we have an extension object already bound to this node
852
- // the client is asking us to replace the object entierly by a new one
853
+ // the client is asking us to replace the object entirely by a new one
853
854
  // const ext = dataValue.value.value;
854
855
  this._internal_set_dataValue(dataValue);
855
856
  return;
@@ -867,6 +868,11 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
867
868
  }
868
869
  }
869
870
 
871
+ private adjustDataValueStatusCode(dataValue: DataValue): StatusCode {
872
+ const statusCode = adjustDataValueStatusCode(this, dataValue, (this as any).acceptValueOutOfRange || false);
873
+ return statusCode;
874
+ }
875
+
870
876
  public writeValue(
871
877
  context: ISessionContext,
872
878
  dataValue: DataValue,
@@ -939,6 +945,12 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
939
945
  return callback!(null, statusCode);
940
946
  }
941
947
 
948
+ // adjust dataValue.statusCode based on InstrumentRange and EngineeringUnits
949
+ const statusCode2 = this.adjustDataValueStatusCode(dataValue);
950
+ if (statusCode2.isNotGood()) {
951
+ return callback!(null, statusCode2);
952
+ }
953
+
942
954
  const write_func = this._timestamped_set_func || default_func;
943
955
 
944
956
  if (!write_func) {
@@ -1281,7 +1293,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1281
1293
 
1282
1294
  const readImmediate = (innerCallback: (err: Error | null, dataValue: DataValue) => void) => {
1283
1295
  assert(this.$dataValue instanceof DataValue);
1284
- const dataValue = this.readValue();
1296
+ const dataValue = this.readValue(context);
1285
1297
  innerCallback(null, dataValue);
1286
1298
  };
1287
1299
 
@@ -2046,10 +2058,14 @@ function _default_writable_timestamped_set_func(
2046
2058
  function turn_sync_to_async<T, D, R>(f: (this: T, data: D) => R, numberOfArgs: number) {
2047
2059
  if (f.length <= numberOfArgs) {
2048
2060
  return function (this: T, data: D, callback: (err: Error | null, r?: R) => void) {
2049
- const r = f.call(this, data);
2050
- setImmediate(() => {
2051
- return callback(null, r);
2052
- });
2061
+ try {
2062
+ const r = f.call(this, data);
2063
+ setImmediate(() => {
2064
+ return callback(null, r);
2065
+ });
2066
+ } catch (err) {
2067
+ return callback(err as Error);
2068
+ }
2053
2069
  };
2054
2070
  } else {
2055
2071
  assert(f.length === numberOfArgs + 1);
@@ -2209,6 +2225,10 @@ function _Variable_bind_with_simple_set(this: UAVariableImpl, options: any) {
2209
2225
  errorLog(chalk.yellow("StatusCode.Good is assumed"));
2210
2226
  return callback(err, StatusCodes.Good, timestamped_value);
2211
2227
  }
2228
+ if (statusCode && statusCode.isNotGood()) {
2229
+ // record the value but still record the statusCode !
2230
+ timestamped_value.statusCode = statusCode;
2231
+ }
2212
2232
  callback(err, statusCode, timestamped_value);
2213
2233
  });
2214
2234
  };
@@ -1,38 +0,0 @@
1
- /**
2
- * @module node-opcua-address-space.DataAccess
3
- */
4
- import { assert } from "node-opcua-assert";
5
- import { UAAnalogItem } from "node-opcua-nodeset-ua";
6
- import { StatusCodes } from "node-opcua-status-code";
7
- import { StatusCode } from "node-opcua-status-code";
8
- import { Range } from "node-opcua-types";
9
- import { DataType, Variant } from "node-opcua-variant";
10
- import { UAVariableImpl } from "../ua_variable_impl";
11
-
12
- function validate_value_range(range: Range, variant: Variant) {
13
- if (variant.value < range.low || variant.value > range.high) {
14
- return false;
15
- }
16
- return true;
17
- }
18
-
19
- function checkVariantCompatibilityUAAnalogItem(this: UAVariableImpl, value: Variant): StatusCode {
20
- assert(value instanceof Variant);
21
- // test dataType
22
- if (!this._validate_DataType(value.dataType)) {
23
- return StatusCodes.BadTypeMismatch;
24
- }
25
- const analogItem = this as unknown as UAAnalogItem<any, any>;
26
- // AnalogDataItem
27
- if (analogItem.instrumentRange) {
28
- if (!validate_value_range(analogItem.instrumentRange.readValue().value.value as Range, value)) {
29
- return StatusCodes.BadOutOfRange;
30
- }
31
- }
32
- return StatusCodes.Good;
33
- }
34
-
35
- /**
36
- * extend default checkVariantCompatibility on base class with this one
37
- */
38
- UAVariableImpl.prototype.checkVariantCompatibility = checkVariantCompatibilityUAAnalogItem;